|
@@ -32,7 +32,6 @@ class MonthlyActiveUsersStore(SQLBaseStore):
|
|
|
super(MonthlyActiveUsersStore, self).__init__(None, hs)
|
|
|
self._clock = hs.get_clock()
|
|
|
self.hs = hs
|
|
|
- self.reserved_users = ()
|
|
|
# Do not add more reserved users than the total allowable number
|
|
|
self._new_transaction(
|
|
|
dbconn,
|
|
@@ -51,7 +50,6 @@ class MonthlyActiveUsersStore(SQLBaseStore):
|
|
|
txn (cursor):
|
|
|
threepids (list[dict]): List of threepid dicts to reserve
|
|
|
"""
|
|
|
- reserved_user_list = []
|
|
|
|
|
|
for tp in threepids:
|
|
|
user_id = self.get_user_id_by_threepid_txn(txn, tp["medium"], tp["address"])
|
|
@@ -60,10 +58,8 @@ class MonthlyActiveUsersStore(SQLBaseStore):
|
|
|
is_support = self.is_support_user_txn(txn, user_id)
|
|
|
if not is_support:
|
|
|
self.upsert_monthly_active_user_txn(txn, user_id)
|
|
|
- reserved_user_list.append(user_id)
|
|
|
else:
|
|
|
logger.warning("mau limit reserved threepid %s not found in db" % tp)
|
|
|
- self.reserved_users = tuple(reserved_user_list)
|
|
|
|
|
|
@defer.inlineCallbacks
|
|
|
def reap_monthly_active_users(self):
|
|
@@ -74,8 +70,11 @@ class MonthlyActiveUsersStore(SQLBaseStore):
|
|
|
Deferred[]
|
|
|
"""
|
|
|
|
|
|
- def _reap_users(txn):
|
|
|
- # Purge stale users
|
|
|
+ def _reap_users(txn, reserved_users):
|
|
|
+ """
|
|
|
+ Args:
|
|
|
+ reserved_users (tuple): reserved users to preserve
|
|
|
+ """
|
|
|
|
|
|
thirty_days_ago = int(self._clock.time_msec()) - (1000 * 60 * 60 * 24 * 30)
|
|
|
query_args = [thirty_days_ago]
|
|
@@ -83,20 +82,19 @@ class MonthlyActiveUsersStore(SQLBaseStore):
|
|
|
|
|
|
# Need if/else since 'AND user_id NOT IN ({})' fails on Postgres
|
|
|
# when len(reserved_users) == 0. Works fine on sqlite.
|
|
|
- if len(self.reserved_users) > 0:
|
|
|
+ if len(reserved_users) > 0:
|
|
|
# questionmarks is a hack to overcome sqlite not supporting
|
|
|
# tuples in 'WHERE IN %s'
|
|
|
- questionmarks = "?" * len(self.reserved_users)
|
|
|
+ question_marks = ",".join("?" * len(reserved_users))
|
|
|
|
|
|
- query_args.extend(self.reserved_users)
|
|
|
- sql = base_sql + """ AND user_id NOT IN ({})""".format(
|
|
|
- ",".join(questionmarks)
|
|
|
- )
|
|
|
+ query_args.extend(reserved_users)
|
|
|
+ sql = base_sql + " AND user_id NOT IN ({})".format(question_marks)
|
|
|
else:
|
|
|
sql = base_sql
|
|
|
|
|
|
txn.execute(sql, query_args)
|
|
|
|
|
|
+ max_mau_value = self.hs.config.max_mau_value
|
|
|
if self.hs.config.limit_usage_by_mau:
|
|
|
# If MAU user count still exceeds the MAU threshold, then delete on
|
|
|
# a least recently active basis.
|
|
@@ -106,31 +104,52 @@ class MonthlyActiveUsersStore(SQLBaseStore):
|
|
|
# While Postgres does not require 'LIMIT', but also does not support
|
|
|
# negative LIMIT values. So there is no way to write it that both can
|
|
|
# support
|
|
|
- safe_guard = self.hs.config.max_mau_value - len(self.reserved_users)
|
|
|
- # Must be greater than zero for postgres
|
|
|
- safe_guard = safe_guard if safe_guard > 0 else 0
|
|
|
- query_args = [safe_guard]
|
|
|
-
|
|
|
- base_sql = """
|
|
|
- DELETE FROM monthly_active_users
|
|
|
- WHERE user_id NOT IN (
|
|
|
- SELECT user_id FROM monthly_active_users
|
|
|
- ORDER BY timestamp DESC
|
|
|
- LIMIT ?
|
|
|
+ if len(reserved_users) == 0:
|
|
|
+ sql = """
|
|
|
+ DELETE FROM monthly_active_users
|
|
|
+ WHERE user_id NOT IN (
|
|
|
+ SELECT user_id FROM monthly_active_users
|
|
|
+ ORDER BY timestamp DESC
|
|
|
+ LIMIT ?
|
|
|
)
|
|
|
- """
|
|
|
+ """
|
|
|
+ txn.execute(sql, (max_mau_value,))
|
|
|
# Need if/else since 'AND user_id NOT IN ({})' fails on Postgres
|
|
|
# when len(reserved_users) == 0. Works fine on sqlite.
|
|
|
- if len(self.reserved_users) > 0:
|
|
|
- query_args.extend(self.reserved_users)
|
|
|
- sql = base_sql + """ AND user_id NOT IN ({})""".format(
|
|
|
- ",".join(questionmarks)
|
|
|
- )
|
|
|
else:
|
|
|
- sql = base_sql
|
|
|
- txn.execute(sql, query_args)
|
|
|
+ # Must be >= 0 for postgres
|
|
|
+ num_of_non_reserved_users_to_remove = max(
|
|
|
+ max_mau_value - len(reserved_users), 0
|
|
|
+ )
|
|
|
+
|
|
|
+ # It is important to filter reserved users twice to guard
|
|
|
+ # against the case where the reserved user is present in the
|
|
|
+ # SELECT, meaning that a legitmate mau is deleted.
|
|
|
+ sql = """
|
|
|
+ DELETE FROM monthly_active_users
|
|
|
+ WHERE user_id NOT IN (
|
|
|
+ SELECT user_id FROM monthly_active_users
|
|
|
+ WHERE user_id NOT IN ({})
|
|
|
+ ORDER BY timestamp DESC
|
|
|
+ LIMIT ?
|
|
|
+ )
|
|
|
+ AND user_id NOT IN ({})
|
|
|
+ """.format(
|
|
|
+ question_marks, question_marks
|
|
|
+ )
|
|
|
+
|
|
|
+ query_args = [
|
|
|
+ *reserved_users,
|
|
|
+ num_of_non_reserved_users_to_remove,
|
|
|
+ *reserved_users,
|
|
|
+ ]
|
|
|
|
|
|
- yield self.runInteraction("reap_monthly_active_users", _reap_users)
|
|
|
+ txn.execute(sql, query_args)
|
|
|
+
|
|
|
+ reserved_users = yield self.get_registered_reserved_users()
|
|
|
+ yield self.runInteraction(
|
|
|
+ "reap_monthly_active_users", _reap_users, reserved_users
|
|
|
+ )
|
|
|
# It seems poor to invalidate the whole cache, Postgres supports
|
|
|
# 'Returning' which would allow me to invalidate only the
|
|
|
# specific users, but sqlite has no way to do this and instead
|
|
@@ -159,21 +178,25 @@ class MonthlyActiveUsersStore(SQLBaseStore):
|
|
|
return self.runInteraction("count_users", _count_users)
|
|
|
|
|
|
@defer.inlineCallbacks
|
|
|
- def get_registered_reserved_users_count(self):
|
|
|
- """Of the reserved threepids defined in config, how many are associated
|
|
|
+ def get_registered_reserved_users(self):
|
|
|
+ """Of the reserved threepids defined in config, which are associated
|
|
|
with registered users?
|
|
|
|
|
|
Returns:
|
|
|
- Defered[int]: Number of real reserved users
|
|
|
+ Defered[list]: Real reserved users
|
|
|
"""
|
|
|
- count = 0
|
|
|
- for tp in self.hs.config.mau_limits_reserved_threepids:
|
|
|
+ users = []
|
|
|
+
|
|
|
+ for tp in self.hs.config.mau_limits_reserved_threepids[
|
|
|
+ : self.hs.config.max_mau_value
|
|
|
+ ]:
|
|
|
user_id = yield self.hs.get_datastore().get_user_id_by_threepid(
|
|
|
tp["medium"], tp["address"]
|
|
|
)
|
|
|
if user_id:
|
|
|
- count = count + 1
|
|
|
- return count
|
|
|
+ users.append(user_id)
|
|
|
+
|
|
|
+ return users
|
|
|
|
|
|
@defer.inlineCallbacks
|
|
|
def upsert_monthly_active_user(self, user_id):
|