# Buggy code
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]
    base_sql = "DELETE FROM monthly_active_users WHERE timestamp < ?"

    # Need if/else since 'AND user_id NOT IN ({})' fails on Postgres
    # when len(reserved_users) == 0. Works fine on sqlite.
    if len(reserved_users) > 0:
        # questionmarks is a hack to overcome sqlite not supporting
        # tuples in 'WHERE IN %s'
        question_marks = ",".join("?" * len(reserved_users))

        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.
        # Note it is not possible to write this query using OFFSET due to
        # incompatibilities in how sqlite and postgres support the feature.
        # sqlite requires 'LIMIT -1 OFFSET ?', the LIMIT must be present
        # 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
        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.
        else:
            # 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,
            ]

            txn.execute(sql, query_args)


# Traceback (exceptions.AttributeError)
Feb 13 17:56:49 ip-10-1-2-232 matrix_synapse[3223]: 2019-02-13 17:56:49,005 - synapse.http.server - 112 - ERROR - PUT-362- Failed handle request via <function _async_render at 0x7ff0363d32a8>: <XForwardedForRequest at 0x7ff02cca4b48 method=u'PUT' uri=u'/_matrix/client/r0/rooms/<<ROOM ID>>/send/m.room.message/163970?user_id=<<USER ID>>&amp;access_token=<redacted>&amp;ts=1477692257000' clientproto=u'HTTP/1.1' site=18095>: Traceback (most recent call last):
Feb 13 17:56:49 ip-10-1-2-232 matrix_synapse[3223]:   File "/opt/synapse/env/local/lib/python2.7/site-packages/twisted/internet/defer.py", line 654, in _runCallbacks
Feb 13 17:56:49 ip-10-1-2-232 matrix_synapse[3223]:     current.result = callback(current.result, *args, **kw)
Feb 13 17:56:49 ip-10-1-2-232 matrix_synapse[3223]:   File "/opt/synapse/env/local/lib/python2.7/site-packages/twisted/internet/defer.py", line 1475, in gotResult
Feb 13 17:56:49 ip-10-1-2-232 matrix_synapse[3223]:     _inlineCallbacks(r, g, status)
Feb 13 17:56:49 ip-10-1-2-232 matrix_synapse[3223]:   File "/opt/synapse/env/local/lib/python2.7/site-packages/twisted/internet/defer.py", line 1416, in _inlineCallbacks
Feb 13 17:56:49 ip-10-1-2-232 matrix_synapse[3223]:     result = result.throwExceptionIntoGenerator(g)
Feb 13 17:56:49 ip-10-1-2-232 matrix_synapse[3223]:   File "/opt/synapse/env/local/lib/python2.7/site-packages/twisted/python/failure.py", line 491, in throwExceptionIntoGenerator
Feb 13 17:56:49 ip-10-1-2-232 matrix_synapse[3223]:     return g.throw(self.type, self.value, self.tb)
Feb 13 17:56:49 ip-10-1-2-232 matrix_synapse[3223]: --- <exception caught here> ---
Feb 13 17:56:49 ip-10-1-2-232 matrix_synapse[3223]:   File "/opt/synapse/env/local/lib/python2.7/site-packages/synapse/http/server.py", line 81, in wrapped_request_handler
Feb 13 17:56:49 ip-10-1-2-232 matrix_synapse[3223]:     yield h(self, request)
Feb 13 17:56:49 ip-10-1-2-232 matrix_synapse[3223]:   File "/opt/synapse/env/local/lib/python2.7/site-packages/twisted/internet/defer.py", line 1416, in _inlineCallbacks
Feb 13 17:56:49 ip-10-1-2-232 matrix_synapse[3223]:     result = result.throwExceptionIntoGenerator(g)
Feb 13 17:56:49 ip-10-1-2-232 matrix_synapse[3223]:   File "/opt/synapse/env/local/lib/python2.7/site-packages/twisted/python/failure.py", line 491, in throwExceptionIntoGenerator
Feb 13 17:56:49 ip-10-1-2-232 matrix_synapse[3223]:     return g.throw(self.type, self.value, self.tb)
Feb 13 17:56:49 ip-10-1-2-232 matrix_synapse[3223]:   File "/opt/synapse/env/local/lib/python2.7/site-packages/synapse/http/server.py", line 316, in _async_render
Feb 13 17:56:49 ip-10-1-2-232 matrix_synapse[3223]:     callback_return = yield callback(request, **kwargs)
Feb 13 17:56:49 ip-10-1-2-232 matrix_synapse[3223]:   File "/opt/synapse/env/local/lib/python2.7/site-packages/twisted/internet/defer.py", line 1416, in _inlineCallbacks
Feb 13 17:56:49 ip-10-1-2-232 matrix_synapse[3223]:     result = result.throwExceptionIntoGenerator(g)
Feb 13 17:56:49 ip-10-1-2-232 matrix_synapse[3223]:   File "/opt/synapse/env/local/lib/python2.7/site-packages/twisted/python/failure.py", line 491, in throwExceptionIntoGenerator
Feb 13 17:56:49 ip-10-1-2-232 matrix_synapse[3223]:     return g.throw(self.type, self.value, self.tb)
Feb 13 17:56:49 ip-10-1-2-232 matrix_synapse[3223]:   File "/opt/synapse/env/local/lib/python2.7/site-packages/synapse/rest/client/v1/room.py", line 217, in on_POST
Feb 13 17:56:49 ip-10-1-2-232 matrix_synapse[3223]:     txn_id=txn_id,
Feb 13 17:56:49 ip-10-1-2-232 matrix_synapse[3223]:   File "/opt/synapse/env/local/lib/python2.7/site-packages/twisted/internet/defer.py", line 1416, in _inlineCallbacks
Feb 13 17:56:49 ip-10-1-2-232 matrix_synapse[3223]:     result = result.throwExceptionIntoGenerator(g)
Feb 13 17:56:49 ip-10-1-2-232 matrix_synapse[3223]:   File "/opt/synapse/env/local/lib/python2.7/site-packages/twisted/python/failure.py", line 491, in throwExceptionIntoGenerator
Feb 13 17:56:49 ip-10-1-2-232 matrix_synapse[3223]:     return g.throw(self.type, self.value, self.tb)
Feb 13 17:56:49 ip-10-1-2-232 matrix_synapse[3223]:   File "/opt/synapse/env/local/lib/python2.7/site-packages/synapse/handlers/message.py", line 488, in create_and_send_nonmember_event
Feb 13 17:56:49 ip-10-1-2-232 matrix_synapse[3223]:     txn_id=txn_id
Feb 13 17:56:49 ip-10-1-2-232 matrix_synapse[3223]:   File "/opt/synapse/env/local/lib/python2.7/site-packages/twisted/internet/defer.py", line 1416, in _inlineCallbacks
Feb 13 17:56:49 ip-10-1-2-232 matrix_synapse[3223]:     result = result.throwExceptionIntoGenerator(g)
Feb 13 17:56:49 ip-10-1-2-232 matrix_synapse[3223]:   File "/opt/synapse/env/local/lib/python2.7/site-packages/twisted/python/failure.py", line 491, in throwExceptionIntoGenerator
Feb 13 17:56:49 ip-10-1-2-232 matrix_synapse[3223]:     return g.throw(self.type, self.value, self.tb)
Feb 13 17:56:49 ip-10-1-2-232 matrix_synapse[3223]:   File "/opt/synapse/env/local/lib/python2.7/site-packages/synapse/handlers/message.py", line 279, in create_event
Feb 13 17:56:49 ip-10-1-2-232 matrix_synapse[3223]:     yield self.auth.check_auth_blocking(requester.user.to_string())
Feb 13 17:56:49 ip-10-1-2-232 matrix_synapse[3223]:   File "/opt/synapse/env/local/lib/python2.7/site-packages/twisted/internet/defer.py", line 1418, in _inlineCallbacks
Feb 13 17:56:49 ip-10-1-2-232 matrix_synapse[3223]:     result = g.send(result)
Feb 13 17:56:49 ip-10-1-2-232 matrix_synapse[3223]:   File "/opt/synapse/env/local/lib/python2.7/site-packages/synapse/api/auth.py", line 812, in check_auth_blocking
Feb 13 17:56:49 ip-10-1-2-232 matrix_synapse[3223]:     timestamp = yield self.store.user_last_seen_monthly_active(user_id)
Feb 13 17:56:49 ip-10-1-2-232 matrix_synapse[3223]: exceptions.AttributeError: 'EventCreatorSlavedStore' object has no attribute 'user_last_seen_monthly_active'

# Fixed code
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]
    base_sql = "DELETE FROM monthly_active_users WHERE timestamp < ?"

    # Need if/else since 'AND user_id NOT IN ({})' fails on Postgres
    # when len(reserved_users) == 0. Works fine on sqlite.
    if len(reserved_users) > 0:
        # questionmarks is a hack to overcome sqlite not supporting
        # tuples in 'WHERE IN %s'
        question_marks = ",".join("?" * len(reserved_users))

        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.
        # Note it is not possible to write this query using OFFSET due to
        # incompatibilities in how sqlite and postgres support the feature.
        # sqlite requires 'LIMIT -1 OFFSET ?', the LIMIT must be present
        # 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
        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.
        else:
            # 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,
            ]

            txn.execute(sql, query_args)

    # 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
    # I would need to SELECT and the DELETE which without locking
    # is racy.
    # Have resolved to invalidate the whole cache for now and do
    # something about it if and when the perf becomes significant
    self._invalidate_all_cache_and_stream(txn, self.user_last_seen_monthly_active)
    self._invalidate_cache_and_stream(txn, self.get_monthly_active_count, ())