Commit Graph

21 Commits

Author SHA1 Message Date
Tim Abbott c6f04a2c75 Do memcached accounting on the memcached repopulating commands.
(imported from commit 56b30c04e7aee9c9d8493d872b3b367f2be14262)
2013-05-13 17:11:31 -04:00
Zev Benjamin 3e1ec5d7c9 Increase size of initial message cache fetch and exclude tabbott/extra's messages
(imported from commit 59544aa3adfb05f50ca69e56f37f57944dfa0b81)
2013-04-25 13:33:51 -04:00
Zev Benjamin a1634b12d3 Increase efficiency of initial message cache query
In repeated trials, the initial data fetch used to take about 1100ms.
In practice, it was often taking >2000ms, probably due to caching
effects.  This commit cuts the time down to about 300ms in repeated
trials.

Note that the semantics are changed slightly in that we may no longer
get exactly 25000 messages.  However, holes in the message_id
sequence are currently very rare or non-existent so this shouldn't be
a problem and we don't care about the exact number of messages
anyway.

I believe the problem was that the query planner was unable to
effectively use the LIMIT clause to figure out that only a small
subset of zephyr_message was going to be needed.  Thus, it planned
for operating on the entire table and decided it could not use a more
efficient plan because work_mem, although large, would not be large
enough to execute the query over all of zephyr_message.

The original query was:

SELECT "zephyr_message"."id", "zephyr_message"."sender_id", "zephyr_message"."recipient_id", "zephyr_message"."subject", "zephyr_message"."content", "zephyr_message"."rendered_content", "zephyr_message"."rendered_content_version", "zephyr_message"."pub_date", "zephyr_message"."sending_client_id", "zephyr_userprofile"."id", "zephyr_userprofile"."password", "zephyr_userprofile"."last_login", "zephyr_userprofile"."email", "zephyr_userprofile"."is_staff", "zephyr_userprofile"."is_active", "zephyr_userprofile"."date_joined", "zephyr_userprofile"."full_name", "zephyr_userprofile"."short_name", "zephyr_userprofile"."pointer", "zephyr_userprofile"."last_pointer_updater", "zephyr_userprofile"."realm_id", "zephyr_userprofile"."api_key", "zephyr_userprofile"."enable_desktop_notifications", "zephyr_userprofile"."enter_sends", "zephyr_userprofile"."tutorial_status", "zephyr_realm"."id", "zephyr_realm"."domain", "zephyr_realm"."restricted_to_domain", "zephyr_recipient"."id", "zephyr_recipient"."type_id", "zephyr_recipient"."type", "zephyr_client"."id", "zephyr_client"."name" FROM "zephyr_message" INNER JOIN "zephyr_userprofile" ON ( "zephyr_message"."sender_id" = "zephyr_userprofile"."id" ) INNER JOIN "zephyr_realm" ON ( "zephyr_userprofile"."realm_id" = "zephyr_realm"."id" ) INNER JOIN "zephyr_recipient" ON ( "zephyr_message"."recipient_id" = "zephyr_recipient"."id" ) INNER JOIN "zephyr_client" ON ( "zephyr_message"."sending_client_id" = "zephyr_client"."id" ) ORDER BY "zephyr_message"."id" DESC LIMIT 25000;

with query plan:
 Limit  (cost=0.00..27120.95 rows=25000 width=362) (actual time=0.051..1121.282 rows=25000 loops=1)
   ->  Nested Loop  (cost=0.00..5330872.99 rows=4913981 width=362) (actual time=0.048..1081.014 rows=25000 loops=1)
         ->  Nested Loop  (cost=0.00..3932643.31 rows=4913981 width=344) (actual time=0.042..926.398 rows=25000 loops=1)
               ->  Nested Loop  (cost=0.00..2550275.29 rows=4913981 width=334) (actual time=0.035..752.524 rows=25000 loops=1)
                     Join Filter: (zephyr_message.sending_client_id = zephyr_client.id)
                     ->  Nested Loop  (cost=0.00..1739467.29 rows=4913981 width=320) (actual time=0.024..217.348 rows=25000 loops=1)
                           ->  Index Scan Backward using zephyr_message_pkey on zephyr_message  (cost=0.00..362510.09 rows=4913981 width=156) (actual time=0.014..42.097 rows=25000 loops=1)
                           ->  Index Scan using zephyr_userprofile_pkey on zephyr_userprofile  (cost=0.00..0.27 rows=1 width=164) (actual time=0.003..0.004 rows=1 loops=25000)
                                 Index Cond: (id = zephyr_message.sender_id)
                     ->  Materialize  (cost=0.00..1.17 rows=11 width=14) (actual time=0.001..0.010 rows=11 loops=25000)
                           ->  Seq Scan on zephyr_client  (cost=0.00..1.11 rows=11 width=14) (actual time=0.002..0.010 rows=11 loops=1)
               ->  Index Scan using zephyr_recipient_pkey on zephyr_recipient  (cost=0.00..0.27 rows=1 width=10) (actual time=0.002..0.003 rows=1 loops=25000)
                     Index Cond: (id = zephyr_message.recipient_id)
         ->  Index Scan using zephyr_realm_pkey on zephyr_realm  (cost=0.00..0.27 rows=1 width=18) (actual time=0.002..0.003 rows=1 loops=25000)
               Index Cond: (id = zephyr_userprofile.realm_id)
 Total runtime: 1141.408 ms

In the new code, we do two queries:

SELECT "zephyr_message"."id" FROM "zephyr_message" ORDER BY "zephyr_message"."id" DESC LIMIT 1

followed by:

SELECT "zephyr_message"."id", "zephyr_message"."sender_id", "zephyr_message"."recipient_id", "zephyr_message"."subject", "zephyr_message"."content", "zephyr_message"."rendered_content", "zephyr_message"."rendered_content_version", "zephyr_message"."pub_date", "zephyr_message"."sending_client_id", "zephyr_userprofile"."id", "zephyr_userprofile"."password", "zephyr_userprofile"."last_login", "zephyr_userprofile"."email", "zephyr_userprofile"."is_staff", "zephyr_userprofile"."is_active", "zephyr_userprofile"."date_joined", "zephyr_userprofile"."full_name", "zephyr_userprofile"."short_name", "zephyr_userprofile"."pointer", "zephyr_userprofile"."last_pointer_updater", "zephyr_userprofile"."realm_id", "zephyr_userprofile"."api_key", "zephyr_userprofile"."enable_desktop_notifications", "zephyr_userprofile"."enter_sends", "zephyr_userprofile"."tutorial_status", "zephyr_realm"."id", "zephyr_realm"."domain", "zephyr_realm"."restricted_to_domain", "zephyr_recipient"."id", "zephyr_recipient"."type_id", "zephyr_recipient"."type", "zephyr_client"."id", "zephyr_client"."name" FROM "zephyr_message" INNER JOIN "zephyr_userprofile" ON ( "zephyr_message"."sender_id" = "zephyr_userprofile"."id" ) INNER JOIN "zephyr_realm" ON ( "zephyr_userprofile"."realm_id" = "zephyr_realm"."id" ) INNER JOIN "zephyr_recipient" ON ( "zephyr_message"."recipient_id" = "zephyr_recipient"."id" ) INNER JOIN "zephyr_client" ON ( "zephyr_message"."sending_client_id" = "zephyr_client"."id" ) WHERE "zephyr_message"."id" > 4941883

with the message id filled in as the result of the first query.  The
new query differs from the original only in that its ORDER BY and
LIMIT clauses are replaced by a WHERE clause.  The second query has
query plan:

 Hash Join  (cost=709.30..28048.18 rows=20544 width=365) (actual time=41.678..279.261 rows=25041 loops=1)
   Hash Cond: (zephyr_message.recipient_id = zephyr_recipient.id)
   ->  Hash Join  (cost=102.98..27056.66 rows=20544 width=355) (actual time=3.686..190.730 rows=25041 loops=1)
         Hash Cond: (zephyr_message.sending_client_id = zephyr_client.id)
         ->  Hash Join  (cost=101.73..26772.94 rows=20544 width=341) (actual time=3.649..143.695 rows=25041 loops=1)
               Hash Cond: (zephyr_userprofile.realm_id = zephyr_realm.id)
               ->  Hash Join  (cost=99.99..26488.71 rows=20544 width=323) (actual time=3.578..96.746 rows=25041 loops=1)
                     Hash Cond: (zephyr_message.sender_id = zephyr_userprofile.id)
                     ->  Index Scan using zephyr_message_pkey on zephyr_message  (cost=0.00..26106.24 rows=20544 width=159) (actual time=0.017..41.980 rows=25041 loops=1)
                           Index Cond: (id > 4941883)
                     ->  Hash  (cost=83.33..83.33 rows=1333 width=164) (actual time=3.548..3.548 rows=1333 loops=1)
                           Buckets: 1024  Batches: 1  Memory Usage: 275kB
                           ->  Seq Scan on zephyr_userprofile  (cost=0.00..83.33 rows=1333 width=164) (actual time=0.006..1.646 rows=1333 loops=1)
               ->  Hash  (cost=1.33..1.33 rows=33 width=18) (actual time=0.064..0.064 rows=33 loops=1)
                     Buckets: 1024  Batches: 1  Memory Usage: 2kB
                     ->  Seq Scan on zephyr_realm  (cost=0.00..1.33 rows=33 width=18) (actual time=0.003..0.033 rows=33 loops=1)
         ->  Hash  (cost=1.11..1.11 rows=11 width=14) (actual time=0.027..0.027 rows=11 loops=1)
               Buckets: 1024  Batches: 1  Memory Usage: 1kB
               ->  Seq Scan on zephyr_client  (cost=0.00..1.11 rows=11 width=14) (actual time=0.003..0.013 rows=11 loops=1)
   ->  Hash  (cost=335.03..335.03 rows=21703 width=10) (actual time=37.974..37.974 rows=21761 loops=1)
         Buckets: 4096  Batches: 1  Memory Usage: 893kB
         ->  Seq Scan on zephyr_recipient  (cost=0.00..335.03 rows=21703 width=10) (actual time=0.004..18.443 rows=21761 loops=1)
 Total runtime: 299.300 ms

(imported from commit b2a70cccc47be7970df407c6be00eccd2e8be82a)
2013-04-25 13:25:15 -04:00
Tim Abbott 102988e430 Refill the Session cache after restarting the server.
The fact that we were dumping this cache and not refilling it seems to
be one of the causes of Tornado restarts being a lot slower on prod
than on local systems.

(imported from commit a32a759f4dfb591706ede1cce2d38f5c3704193c)
2013-04-24 10:44:56 -04:00
Luke Faraone 71a91197fa Enable absolute imports.
See PEP 328[1] for details. This feature was introduced in Python 2.5 and
will become mandatory in Python 3.

[1]: http://www.python.org/dev/peps/pep-0328

(imported from commit 7444eeba8a08d5f91b94c7921848f2274979bd76)
2013-04-23 09:51:17 -07:00
Tim Abbott 1cec86eb2d [manual] Remove now-unused User model.
I think all that one needs to do to deploy this commit is on developer
laptops, run `generate-fixtures --force`.

(imported from commit 34916341435fef0875b5a2c7f53c2f5606cd16cd)
2013-04-02 12:57:10 -04:00
Tim Abbott 1443edce00 Access the UserProfile's new email field rather than using User.
This is preparatory for stopping using the User model.

(imported from commit a1b0808c8cc2ddd19a25163f91c4f18620c9ce90)
2013-04-02 12:07:08 -04:00
Tim Abbott e8aa77c9b4 Set timeouts for our memcached caches.
The policy this implements is:
* 1 week for most persistent data (Clients, etc.)
* 1 day for messages

(imported from commit d57bb2c6b9626ffa2155c6d0ef9b60827d1f2381)
2013-03-28 07:36:10 -04:00
Tim Abbott 4502e563d1 Reduce code duplication for coding filling memcached caches.
(imported from commit e7e053c6fa27fd7a1143696196e9ee974ec91c8f)
2013-03-27 12:59:24 -04:00
Tim Abbott 8fbc98601b Populate cache of Huddle objects on server restart.
(imported from commit 3214cfa70d0de01c42028a9d025f7607f609081b)
2013-03-27 12:59:24 -04:00
Tim Abbott b2e76f50b9 Populate cache of Client objects on server restart.
(imported from commit 6d0ef309dadcd6c24fc9e17b0859becb053adccf)
2013-03-27 12:59:23 -04:00
Tim Abbott 36f09729e6 Run the memcached cache filling jobs in separate processes.
This will significantly improve the speed with which the memcached
caches are filled.

(imported from commit 445fec710a00b3d19b7962beddf89d4a75322eab)
2013-03-27 12:59:23 -04:00
Tim Abbott a774598a10 Populate cache of Recipient objects on server restart.
(imported from commit 752851c7c3ee565201dd59fb0455c82a8885d42d)
2013-03-27 12:59:23 -04:00
Tim Abbott cc065a3c90 Populate cache of Stream objects on server restart.
(imported from commit 131eeddc5ebe5cc0db2554514a730d2750078012)
2013-03-27 12:59:23 -04:00
Tim Abbott b38a538f8c Rename userprofile_by_foo cache keys to user_profile_by_foo.
(imported from commit ef398abc48c9b81a3d339ffdce00bae274246d28)
2013-03-18 16:15:11 -04:00
Tim Abbott 9909c8dc19 send_message: Use a memcached for the UserProfile-by-id cache.
This cache should save 2 database queries whenever we send a private
message.  However, previously it was per-process (which meant it was
mostly useless) and also buggy (it never stored anything in the cache,
so that it was completely useless).  Switching this to our standard
memcached setup will address both problems.

(imported from commit 1d807f30704bccf28de33a80523488aedc58a9be)
2013-03-18 16:15:11 -04:00
Tim Abbott ec07184721 cache_helpers: Fill in memcached message cache in batches of 1000.
This cache filling code takes about 5 seconds to run, which means it
will finish before Tornado starts reading from this cache, but if that
were to change, it would be much better to have made at least some
progress.

(imported from commit 60a3420cdb9ddf331d83573a3fdb6be1a5ee5a4f)
2013-03-15 16:37:41 -04:00
Tim Abbott e78ef47487 message_cache: Query related models to avoid more queries later.
(imported from commit c1442165c68e9eef1cb5d310624c1021c1a57350)
2013-03-15 16:37:40 -04:00
Tim Abbott d88591c19c cache: Fix the select_related() query name for sending_client.
Previously we were calling select_related() on Message.client, which
doesn't exist.  It seems kinda poor that this doesn't raise an
exception.

I believe this issue was causing us to do very large numbers of
database queries during get_updates calls during server restarts.

(imported from commit b79bd698820fbd9dd82bd61fc175c32cd5ce6d05)
2013-03-15 16:37:40 -04:00
Tim Abbott 76837ebe2c fill_memcached_caches: Also fill the caches of User/UserProfile objects.
Since we flush memcached when we do a server restart, the flurry of
get_updates requests that fly in afterwards are all cache misses for
getting the User/UserProfile objects, so Tornado ends up spending
around 70ms per get_updates request rather than the usual 1-2ms.

So this should substantially improve our Tornado performance around
server restarts.

(imported from commit 07b8126bdfd4ff14e4c3362f9eda1fe5fd571c5b)
2013-03-14 15:07:42 -04:00
Tim Abbott cf58ee833a Rename lib/message_cache.py to lib/cache_helpers.py
(imported from commit 591e427aee47aea58b50d6c3fbd6a05cfe9378ea)
2013-03-14 15:07:41 -04:00