diff --git a/pkg/services/sessionstorage/config.go b/pkg/services/sessionstorage/config.go index e1f28d0..9d25b99 100644 --- a/pkg/services/sessionstorage/config.go +++ b/pkg/services/sessionstorage/config.go @@ -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), } } diff --git a/pkg/services/sessionstorage/service.go b/pkg/services/sessionstorage/service.go index 8edfb92..da98ea5 100644 --- a/pkg/services/sessionstorage/service.go +++ b/pkg/services/sessionstorage/service.go @@ -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. @@ -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, @@ -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. @@ -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. diff --git a/pkg/services/sessionstorage/service_test.go b/pkg/services/sessionstorage/service_test.go index 3408382..ee80eed 100644 --- a/pkg/services/sessionstorage/service_test.go +++ b/pkg/services/sessionstorage/service_test.go @@ -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) @@ -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) @@ -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()) @@ -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) { @@ -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")} @@ -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") @@ -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) @@ -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) @@ -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. @@ -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. @@ -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)