add mau_trial_days config param.

only consider users MAU after they've been around N days.
This is an alternative implementation to https://github.com/matrix-org/synapse/pull/3739
as suggested by @neilisfragile, which is much simpler as you just hold off adding
users to the MAU table until they've been active for more than N days.
This commit is contained in:
Matthew Hodgson 2018-08-23 01:39:01 +02:00
parent c7181dcc6c
commit 6dac856411
8 changed files with 55 additions and 21 deletions

View File

@ -793,8 +793,8 @@ class Auth(object):
if self.hs.config.limit_usage_by_mau is True:
# If the user is already part of the MAU cohort
if user_id:
timestamp = yield self.store.user_last_seen_monthly_active(user_id)
if timestamp:
activity = yield self.store.user_last_seen_monthly_active(user_id)
if activity:
return
# Else if there is no room in the MAU bucket, bail
current_mau = yield self.store.get_monthly_active_count()

View File

@ -80,6 +80,9 @@ class ServerConfig(Config):
self.mau_limits_reserved_threepids = config.get(
"mau_limit_reserved_threepids", []
)
self.mau_trial_days = config.get(
"mau_trial_days", 0,
)
# Options to disable HS
self.hs_disabled = config.get("hs_disabled", False)
@ -365,6 +368,7 @@ class ServerConfig(Config):
# Enables monthly active user checking
# limit_usage_by_mau: False
# max_mau_value: 50
# mau_trial_days: 2
#
# Sometimes the server admin will want to ensure certain accounts are
# never blocked by mau checking. These accounts are specified here.

View File

@ -177,19 +177,36 @@ class MonthlyActiveUsersStore(SQLBaseStore):
Arguments:
user_id (str): user to add/update
Return:
Deferred[int] : timestamp since last seen, None if never seen
Deferred[(int,bool)|None] : (time since last seen, bool if trial user)
None if never seen
"""
return(self._simple_select_one_onecol(
table="monthly_active_users",
keyvalues={
"user_id": user_id,
},
retcol="timestamp",
allow_none=True,
desc="user_last_seen_monthly_active",
))
mau_trial_ms = self.hs.config.mau_trial_days * 24 * 60 * 60 * 1000
def _user_last_seen_monthly_active(txn):
sql = """
SELECT timestamp, creation_ts
FROM monthly_active_users LEFT JOIN users
ON monthly_active_users.user_id = users.name
WHERE user_id = ?
"""
txn.execute(sql, (user_id,))
rows = txn.fetchall()
if not rows or not rows[0][0]:
return None
else:
(timestamp, created) = (rows[0][0], rows[0][1])
if not created:
created = 0
trial = (timestamp - created * 1000) < mau_trial_ms
return (timestamp, trial)
return self.runInteraction(
"user_last_seen_monthly_active",
_user_last_seen_monthly_active
)
@defer.inlineCallbacks
def populate_monthly_active_users(self, user_id):
@ -200,7 +217,12 @@ class MonthlyActiveUsersStore(SQLBaseStore):
user_id(str): the user_id to query
"""
if self.hs.config.limit_usage_by_mau:
last_seen_timestamp = yield self.user_last_seen_monthly_active(user_id)
activity = yield self.user_last_seen_monthly_active(user_id)
if activity and activity[1]:
# we don't track trial users in the MAU table.
return
now = self.hs.get_clock().time_msec()
# We want to reduce to the total number of db writes, and are happy
@ -208,9 +230,9 @@ class MonthlyActiveUsersStore(SQLBaseStore):
# We always insert new users (where MAU threshold has not been reached),
# but only update if we have not previously seen the user for
# LAST_SEEN_GRANULARITY ms
if last_seen_timestamp is None:
if activity is None:
count = yield self.get_monthly_active_count()
if count < self.hs.config.max_mau_value:
yield self.upsert_monthly_active_user(user_id)
elif now - last_seen_timestamp > LAST_SEEN_GRANULARITY:
elif now - activity[0] > LAST_SEEN_GRANULARITY:
yield self.upsert_monthly_active_user(user_id)

View File

@ -41,6 +41,7 @@ class AuthTestCase(unittest.TestCase):
self.macaroon_generator = self.hs.get_macaroon_generator()
# MAU tests
self.hs.config.max_mau_value = 50
self.hs.config.mau_trial_days = 0
self.small_number_of_users = 1
self.large_number_of_users = 100
@ -161,14 +162,14 @@ class AuthTestCase(unittest.TestCase):
)
# If in monthly active cohort
self.hs.get_datastore().user_last_seen_monthly_active = Mock(
return_value=defer.succeed(self.hs.get_clock().time_msec())
return_value=defer.succeed((self.hs.get_clock().time_msec(), False))
)
self.hs.get_datastore().get_monthly_active_count = Mock(
return_value=defer.succeed(self.hs.config.max_mau_value)
)
yield self.auth_handler.get_access_token_for_user_id('user_a')
self.hs.get_datastore().user_last_seen_monthly_active = Mock(
return_value=defer.succeed(self.hs.get_clock().time_msec())
return_value=defer.succeed((self.hs.get_clock().time_msec(), False))
)
self.hs.get_datastore().get_monthly_active_count = Mock(
return_value=defer.succeed(self.hs.config.max_mau_value)

View File

@ -54,6 +54,7 @@ class RegistrationTestCase(unittest.TestCase):
self.handler = self.hs.get_handlers().registration_handler
self.store = self.hs.get_datastore()
self.hs.config.max_mau_value = 50
self.hs.config.mau_trial_days = 0
self.lots_of_users = 100
self.small_number_of_users = 1

View File

@ -42,6 +42,7 @@ class SyncTestCase(tests.unittest.TestCase):
self.hs.config.limit_usage_by_mau = True
self.hs.config.max_mau_value = 1
self.hs.config.mau_trial_days = 0
# Check that the happy case does not throw errors
yield self.store.upsert_monthly_active_user(user_id1)

View File

@ -59,6 +59,7 @@ class ClientIpStoreTestCase(tests.unittest.TestCase):
def test_disabled_monthly_active_user(self):
self.hs.config.limit_usage_by_mau = False
self.hs.config.max_mau_value = 50
self.hs.config.mau_trial_days = 0
user_id = "@user:server"
yield self.store.insert_client_ip(
user_id, "access_token", "ip", "user_agent", "device_id"
@ -70,6 +71,7 @@ class ClientIpStoreTestCase(tests.unittest.TestCase):
def test_adding_monthly_active_user_when_full(self):
self.hs.config.limit_usage_by_mau = True
self.hs.config.max_mau_value = 50
self.hs.config.mau_trial_days = 0
lots_of_users = 100
user_id = "@user:server"
@ -86,6 +88,7 @@ class ClientIpStoreTestCase(tests.unittest.TestCase):
def test_adding_monthly_active_user_when_space(self):
self.hs.config.limit_usage_by_mau = True
self.hs.config.max_mau_value = 50
self.hs.config.mau_trial_days = 0
user_id = "@user:server"
active = yield self.store.user_last_seen_monthly_active(user_id)
self.assertFalse(active)
@ -100,6 +103,7 @@ class ClientIpStoreTestCase(tests.unittest.TestCase):
def test_updating_monthly_active_user_when_space(self):
self.hs.config.limit_usage_by_mau = True
self.hs.config.max_mau_value = 50
self.hs.config.mau_trial_days = 0
user_id = "@user:server"
active = yield self.store.user_last_seen_monthly_active(user_id)

View File

@ -30,6 +30,7 @@ class MonthlyActiveUsersTestCase(tests.unittest.TestCase):
def setUp(self):
self.hs = yield setup_test_homeserver(self.addCleanup)
self.store = self.hs.get_datastore()
self.hs.config.mau_trial_days = 0
@defer.inlineCallbacks
def test_initialise_reserved_users(self):
@ -105,13 +106,13 @@ class MonthlyActiveUsersTestCase(tests.unittest.TestCase):
user_id3 = "@user3:server"
result = yield self.store.user_last_seen_monthly_active(user_id1)
self.assertFalse(result == 0)
self.assertFalse(result)
yield self.store.upsert_monthly_active_user(user_id1)
yield self.store.upsert_monthly_active_user(user_id2)
result = yield self.store.user_last_seen_monthly_active(user_id1)
self.assertTrue(result > 0)
self.assertTrue(result[0] > 0)
result = yield self.store.user_last_seen_monthly_active(user_id3)
self.assertFalse(result == 0)
self.assertFalse(result)
@defer.inlineCallbacks
def test_reap_monthly_active_users(self):