Skip to content

Commit

Permalink
add a new sessionstorage option to limit number of sessions per visitor
Browse files Browse the repository at this point in the history
  • Loading branch information
negrel committed Oct 20, 2024
1 parent 44c6ee9 commit 76c504d
Show file tree
Hide file tree
Showing 3 changed files with 65 additions and 24 deletions.
10 changes: 6 additions & 4 deletions pkg/services/sessionstorage/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,16 @@ import (

// Session storage service configuration options.
type Config struct {
gcInterval time.Duration
sessionInactiveTtl time.Duration
gcInterval time.Duration
sessionInactiveTtl time.Duration
maxSessionsPerVisitor uint64
}

// ProvideConfig is a wire provider for session storage configuration.
func ProvideConfig() Config {
return Config{
gcInterval: config.ParseDurationEnvOrDefault("PRISME_SESSIONSTORAGE_GC_INTERVAL", 10*time.Minute),
sessionInactiveTtl: config.ParseDurationEnvOrDefault("PRISME_SESSIONSTORAGE_SESSION_INACTIVE_TTL", 24*time.Hour),
gcInterval: config.ParseDurationEnvOrDefault("PRISME_SESSIONSTORAGE_GC_INTERVAL", 10*time.Minute),
sessionInactiveTtl: config.ParseDurationEnvOrDefault("PRISME_SESSIONSTORAGE_SESSION_INACTIVE_TTL", 24*time.Hour),
maxSessionsPerVisitor: config.ParseUintEnvOrDefault("PRISME_SESSIONSTORAGE_MAX_SERSSIONS_PER_VISITOR", 64, 64),
}
}
19 changes: 11 additions & 8 deletions pkg/services/sessionstorage/service.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,15 +11,12 @@ import (
"github.com/rs/zerolog"
)

type AddPageviewResult struct {
Session event.Session
DuplicatePageview bool
}

// Service define an in memory session storage.
type Service interface {
// InsertSession stores given session.
InsertSession(deviceId uint64, session event.Session)
// InsertSession stores given session in memory. If number of visitor session
// exceed configured max session per visitor, this function returns false and
// session isn't stored.
InsertSession(deviceId uint64, session event.Session) bool
// AddPageview adds a pageview to a session with the given device id
// latest path (referrer). Session is returned along a true flag if it was
// found.
Expand Down Expand Up @@ -124,7 +121,7 @@ func (s *service) getValidSessionEntry(deviceId uint64, latestPath string) *entr
}

// InsertSession implements Service.
func (s *service) InsertSession(deviceId uint64, session event.Session) {
func (s *service) InsertSession(deviceId uint64, session event.Session) bool {
s.mu.Lock()
newEntry := entry{
Session: session,
Expand All @@ -139,6 +136,10 @@ func (s *service) InsertSession(deviceId uint64, session event.Session) {
sessions = make([]entry, 1)
sessions[0] = newEntry
s.data[deviceId] = sessions
} else if len(sessions) >= int(s.cfg.maxSessionsPerVisitor) {
s.mu.Unlock()
// Prevent visitor from creating too many sessions.
return false
} else {
// New session only.

Expand All @@ -164,6 +165,8 @@ func (s *service) InsertSession(deviceId uint64, session event.Session) {
// Compute metrics.
s.metrics.sessionsCounter.With(prometheus.Labels{"type": "inserted"}).Inc()
s.metrics.activeSessions.Inc()

return true
}

// AddPageview implements Service.
Expand Down
60 changes: 48 additions & 12 deletions pkg/services/sessionstorage/service_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,9 @@ import (
func TestService(t *testing.T) {
logger := log.NewLogger("sessionstorage_test", io.Discard, true)
cfg := Config{
gcInterval: 10 * time.Second,
sessionInactiveTtl: 24 * time.Hour,
gcInterval: 10 * time.Second,
sessionInactiveTtl: 24 * time.Hour,
maxSessionsPerVisitor: 64,
}

mustParseUri := testutils.Must(uri.Parse)
Expand All @@ -36,7 +37,8 @@ func TestService(t *testing.T) {
PageviewCount: 1,
}

service.InsertSession(deviceId, session)
ok := service.InsertSession(deviceId, session)
require.True(t, ok)

entry := service.getValidSessionEntry(deviceId, pageUri.Path())
require.NotNil(t, entry)
Expand Down Expand Up @@ -68,13 +70,15 @@ func TestService(t *testing.T) {
PageviewCount: 1,
}

service.InsertSession(deviceId, sessionA)
ok := service.InsertSession(deviceId, sessionA)
require.True(t, ok)

sessionB := sessionA
sessionB.VisitorId = "prisme_YYY"

// Add another session on same path.
service.InsertSession(deviceId, sessionB)
ok = service.InsertSession(deviceId, sessionB)
require.True(t, ok)

// get session returns first matching session, here it is session A.
entry := service.getValidSessionEntry(deviceId, pageUri.Path())
Expand All @@ -94,6 +98,31 @@ func TestService(t *testing.T) {
require.Equal(t, float64(0),
testutils.HistogramSumValue(t, promRegistry, "sessionstorage_sessions_pageviews", nil))
})

t.Run("TooManySession", func(t *testing.T) {
testCfg := cfg
testCfg.maxSessionsPerVisitor = 1

promRegistry := prometheus.NewRegistry()
service := ProvideService(logger, testCfg, promRegistry).(*service)

deviceId := rand.Uint64()
pageUri := mustParseUri("https://example.com")
sessionA := event.Session{
PageUri: pageUri,
VisitorId: "prisme_XXX",
PageviewCount: 1,
}

ok := service.InsertSession(deviceId, sessionA)
require.True(t, ok)

sessionB := sessionA
sessionB.VisitorId = "prisme_YYY"

ok = service.InsertSession(deviceId, sessionB)
require.False(t, ok)
})
})

t.Run("AddPageview", func(t *testing.T) {
Expand All @@ -109,7 +138,8 @@ func TestService(t *testing.T) {
PageviewCount: 1,
}

service.InsertSession(deviceId, sessionV1)
ok := service.InsertSession(deviceId, sessionV1)
require.True(t, ok)

// Referrer doesn't match page uri of created session.
referrer := event.ReferrerUri{Uri: mustParseUri("https://example.com/bar")}
Expand Down Expand Up @@ -148,7 +178,8 @@ func TestService(t *testing.T) {
PageviewCount: 1,
}

service.InsertSession(deviceId, sessionV1)
ok := service.InsertSession(deviceId, sessionV1)
require.True(t, ok)

referrer := event.ReferrerUri{Uri: pageUri}
pageUri = mustParseUri("https://example.com/foo")
Expand Down Expand Up @@ -190,7 +221,8 @@ func TestService(t *testing.T) {
PageviewCount: 1,
}

service.InsertSession(deviceId, session)
ok := service.InsertSession(deviceId, session)
require.True(t, ok)

identifiedSession, ok := service.IdentifySession(deviceId, session.PageUri, "prisme_YYY")
require.True(t, ok)
Expand All @@ -210,7 +242,8 @@ func TestService(t *testing.T) {
PageviewCount: 1,
}

service.InsertSession(deviceId, session)
ok := service.InsertSession(deviceId, session)
require.True(t, ok)

identifiedSession, ok := service.IdentifySession(deviceId, mustParseUri("https://example.com/foo"), "prisme_YYY")
require.False(t, ok)
Expand Down Expand Up @@ -305,7 +338,8 @@ func TestService(t *testing.T) {
require.Equal(t, float64(1),
testutils.GaugeValue(t, promRegistry, "sessionstorage_sessions_wait", nil))

service.InsertSession(deviceId, session)
ok := service.InsertSession(deviceId, session)
require.True(t, ok)
}()

// Wait for session.
Expand Down Expand Up @@ -371,7 +405,8 @@ func TestService(t *testing.T) {
require.Equal(t, float64(1),
testutils.GaugeValue(t, promRegistry, "sessionstorage_sessions_wait", nil))

service.InsertSession(deviceId, session)
ok := service.InsertSession(deviceId, session)
require.True(t, ok)
}()

// Wait for session with /foo path.
Expand Down Expand Up @@ -413,7 +448,8 @@ func TestService(t *testing.T) {
}

// Insert session.
service.InsertSession(deviceId, session)
ok := service.InsertSession(deviceId, session)
require.True(t, ok)

now := time.Now()
actualSession, found := service.WaitSession(deviceId, pageUri, 10*time.Millisecond)
Expand Down

0 comments on commit 76c504d

Please sign in to comment.