|
|
|
@ -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): |
|
|
|
|