Created
March 14, 2018 12:40
-
-
Save paolobueno/1488f597793937d2f8576c6bd19e8042 to your computer and use it in GitHub Desktop.
app metrics benchmarks
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| /* | |
| Tested with data generated by | |
| autocannon -a 150000 -m POST -I -b '{"clientId":"[<id>]","data":{"app":{"appId":"com.example.someApp","sdkVersion":"2.4.6","appVersion":"256"},"device":{"platform":"android","platformVersion":"27"}}}' http://localhost:3000/metrics | |
| Thus creating 150k records. | |
| This uses generated clientIds and the rest of the data is constant, thus it doesn't match very well the expected cardinality, | |
| I'd like to redo these tests with better random data. | |
| For example we wouldn't have unique clientIds per record, and have some variance in device platform, and some less in app info, and also a much bigger spread in event_times. | |
| This probably leads to some skew both in the queries and in the PK/index sizes for now. | |
| Here are the insert performance data: | |
| jsonb with PK: | |
| Stat Avg Stdev Max | |
| Latency (ms) 3.58 5.39 240.86 | |
| Req/Sec 2381.08 909.05 4146 | |
| Bytes/Sec 153 kB 58.2 kB 265 kB | |
| jsonb without PK: | |
| Stat Avg Stdev Max | |
| Latency (ms) 3.74 5.81 302.88 | |
| Req/Sec 2307.85 929.5 4026 | |
| Bytes/Sec 148 kB 59.5 kB 258 kB | |
| relational with PKs: | |
| Stat Avg Stdev Max | |
| Latency (ms) 3.42 2.94 210.82 | |
| Req/Sec 2500.27 618.5 3186 | |
| Bytes/Sec 160 kB 39.7 kB 204 kB | |
| It seems that the bottleneck is in the CPU, so running autocannon with -a <n_of_records> is much faster than -c <concurrency> -d <seconds>. | |
| Also with -a there are no non-200 responses on all implementations. | |
| I wanna look at insertion speeds with a round robin load balancer and multiple instances of the golang server. | |
| */ | |
| -- table size query from https://wiki.postgresql.org/wiki/Disk_Usage | |
| SELECT *, pg_size_pretty(total_bytes) AS total | |
| , pg_size_pretty(index_bytes) AS INDEX | |
| , pg_size_pretty(toast_bytes) AS toast | |
| , pg_size_pretty(table_bytes) AS TABLE | |
| FROM ( | |
| SELECT *, total_bytes-index_bytes-COALESCE(toast_bytes,0) AS table_bytes FROM ( | |
| SELECT c.oid,nspname AS table_schema, relname AS TABLE_NAME | |
| , c.reltuples AS row_estimate | |
| , pg_total_relation_size(c.oid) AS total_bytes | |
| , pg_indexes_size(c.oid) AS index_bytes | |
| , pg_total_relation_size(reltoastrelid) AS toast_bytes | |
| FROM pg_class c | |
| LEFT JOIN pg_namespace n ON n.oid = c.relnamespace | |
| WHERE relkind = 'r' and nspname = 'public' | |
| ) a | |
| ) a; | |
| /* | |
| +-------+----------------+------------------------+----------------+---------------+---------------+---------------+---------------+---------+---------+---------+---------+ | |
| | oid | table_schema | table_name | row_estimate | total_bytes | index_bytes | toast_bytes | table_bytes | total | index | toast | table | | |
| |-------+----------------+------------------------+----------------+---------------+---------------+---------------+---------------+---------+---------+---------+---------| | |
| | 16386 | public | mobilemetrics_app | 150000.0 | 70115328 | 27303936 | <null> | 42811392 | 67 MB | 26 MB | <null> | 41 MB | | |
| | 16395 | public | mobilemetrics_device | 150000.0 | 62636032 | 25862144 | <null> | 36773888 | 60 MB | 25 MB | <null> | 35 MB | | |
| | 16404 | public | mobilemetrics_security | 0.0 | 81920 | 81920 | <null> | 0 | 80 kB | 80 kB | <null> | 0 bytes | | |
| | 16444 | public | mobileappmetrics | 126079.0 | 73785344 | 18161664 | 16384 | 55607296 | 70 MB | 17 MB | 16 kB | 53 MB | | |
| +-------+----------------+------------------------+----------------+---------------+---------------+---------------+---------------+---------+---------+---------+---------+ | |
| */ | |
| -- Original query by Ali | |
| EXPLAIN ANALYZE SELECT | |
| count(distinct latestEntryInTimeRange.clientId) | |
| FROM | |
| (select entry.clientId, entry.event_time, entry.data | |
| from mobileAppMetrics entry | |
| inner join | |
| (select clientId, data->'app'->>'appId' as appId, max(event_time) as latestEntryTime | |
| from mobileAppMetrics | |
| where event_time between '2018-03-14 08:00:00+00' AND '2018-03-14 20:10:00+00' | |
| and data->'app'->>'appId' like 'com.example.someApp' | |
| and data->'device'->>'platform' like 'android' | |
| group by clientId, appId | |
| ) | |
| latestEntry | |
| on entry.clientId = latestEntry.clientId and entry.data->'app'->>'appId' = latestEntry.appId and entry.event_time = latestEntryTime | |
| ) latestEntryInTimeRange | |
| where latestEntryInTimeRange.data->'app' ? 'appId'; | |
| /* | |
| +-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | |
| | QUERY PLAN | |
| |-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | |
| | Aggregate (cost=23419.91..23419.92 rows=1 width=8) (actual time=11780.281..11780.281 rows=1 loops=1) | |
| | -> Nested Loop (cost=23386.34..23419.91 rows=1 width=81) (actual time=4345.876..7627.462 rows=150000 loops=1) | |
| | -> GroupAggregate (cost=23385.92..23386.02 rows=4 width=121) (actual time=4345.802..4454.237 rows=150000 loops=1) | |
| | Group Key: mobileappmetrics.clientid, (((mobileappmetrics.data -> 'app'::text) ->> 'appId'::text)) | |
| | -> Sort (cost=23385.92..23385.93 rows=4 width=121) (actual time=4345.794..4376.307 rows=150000 loops=1) | |
| | Sort Key: mobileappmetrics.clientid, (((mobileappmetrics.data -> 'app'::text) ->> 'appId'::text)) | |
| | Sort Method: external sort Disk: 17896kB | |
| | -> Seq Scan on mobileappmetrics (cost=0.00..23385.88 rows=4 width=121) (actual time=0.076..204.847 rows=150000 loops=1) | |
| | Filter: ((event_time >= '2018-03-14 08:00:00+00'::timestamp with time zone) AND (event_time <= '2018-03-14 20:10:00+00'::timestamp with time zone) AND (((data -> 'app'::text) ->> 'appId'::text) ~~ 'com.example.someApp'::text) AND (((data -> 'dev | |
| | -> Index Scan using mobileappmetrics_pkey on mobileappmetrics entry (cost=0.42..8.45 rows=1 width=829) (actual time=0.020..0.021 rows=1 loops=150000) | |
| | Index Cond: ((clientid = mobileappmetrics.clientid) AND (event_time = (max(mobileappmetrics.event_time)))) | |
| | Filter: (((data -> 'app'::text) ? 'appId'::text) AND ((((mobileappmetrics.data -> 'app'::text) ->> 'appId'::text)) = ((data -> 'app'::text) ->> 'appId'::text))) | |
| | Planning time: 0.470 ms | |
| | Execution time: 11782.979 ms | |
| +-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | |
| */ | |
| -- CTE Query | |
| EXPLAIN ANALYZE WITH a as ( | |
| select clientId, event_time | |
| FROM mobileAppMetrics | |
| WHERE data->'app'->>'appId' like 'com.example.someApp' | |
| and data->'device'->>'platform' like 'android' | |
| ) | |
| SELECT count(distinct a.clientId) | |
| FROM a | |
| INNER JOIN (select clientId, max(event_time) as ltime FROM a where event_time between '2018-03-14 08:00:00+00' AND '2018-03-14 20:10:00+00' GROUP BY clientId) as latest | |
| ON a.clientId = latest.clientId | |
| AND a.event_time = latest.ltime | |
| /* | |
| With PRIMARY KEY(clientid, event_time) | |
| +-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ | |
| | QUERY PLAN | | |
| |-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | |
| | Aggregate (cost=22635.81..22635.82 rows=1 width=8) (actual time=3560.881..3560.881 rows=1 loops=1) | | |
| | CTE a | | |
| | -> Seq Scan on mobileappmetrics (cost=0.00..22635.55 rows=4 width=89) (actual time=0.061..150.962 rows=150000 loops=1) | | |
| | Filter: ((((data -> 'app'::text) ->> 'appId'::text) ~~ 'com.example.someApp'::text) AND (((data -> 'device'::text) ->> 'platform'::text) ~~ 'android'::text)) | | |
| | -> Hash Join (cost=0.14..0.26 rows=1 width=324) (actual time=378.267..516.187 rows=150000 loops=1) | | |
| | Hash Cond: ((a.clientid = a_1.clientid) AND (a.event_time = (max(a_1.event_time)))) | | |
| | -> CTE Scan on a (cost=0.00..0.08 rows=4 width=332) (actual time=0.065..15.654 rows=150000 loops=1) | | |
| | -> Hash (cost=0.12..0.12 rows=1 width=332) (actual time=378.184..378.184 rows=150000 loops=1) | | |
| | Buckets: 32768 (originally 1024) Batches: 8 (originally 1) Memory Usage: 3841kB | | |
| | -> HashAggregate (cost=0.11..0.12 rows=1 width=332) (actual time=291.319..327.576 rows=150000 loops=1) | | |
| | Group Key: a_1.clientid | | |
| | -> CTE Scan on a a_1 (cost=0.00..0.10 rows=1 width=332) (actual time=0.004..221.413 rows=150000 loops=1) | | |
| | Filter: ((event_time >= '2018-03-14 08:00:00+00'::timestamp with time zone) AND (event_time <= '2018-03-14 20:10:00+00'::timestamp with time zone)) | | |
| | Planning time: 0.348 ms | | |
| | Execution time: 3563.164 ms | | |
| +-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ | |
| Without the PK the query takes forever to run because of a n^2 loop | |
| +-------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ | |
| | QUERY PLAN | | |
| |-------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | |
| | Aggregate (cost=40766.71..40766.72 rows=1 width=8) | | |
| | CTE a | | |
| | -> Seq Scan on mobileappmetrics (cost=0.00..40766.50 rows=3 width=89) | | |
| | Filter: ((((data -> 'app'::text) ->> 'appId'::text) ~~ 'com.example.someApp'::text) AND (((data -> 'device'::text) ->> 'platform'::text) ~~ 'android'::text)) | | |
| | -> Nested Loop (cost=0.08..0.21 rows=1 width=324) | | |
| | Join Filter: ((a.clientid = a_1.clientid) AND (a.event_time = (max(a_1.event_time)))) | | |
| | -> HashAggregate (cost=0.08..0.09 rows=1 width=332) | | |
| | Group Key: a_1.clientid | | |
| | -> CTE Scan on a a_1 (cost=0.00..0.08 rows=1 width=332) | | |
| | Filter: ((event_time >= '2018-03-14 08:00:00+00'::timestamp with time zone) AND (event_time <= '2018-03-14 20:10:00+00'::timestamp with time zone)) | | |
| | -> CTE Scan on a (cost=0.00..0.06 rows=3 width=332) | | |
| +-------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ | |
| */ | |
| -- Using @> instead of -> + ->> | |
| EXPLAIN WITH a as ( | |
| select clientId, event_time | |
| FROM mobileAppMetrics | |
| WHERE data @> '{"app":{"appId": "com.example.someApp"}}' | |
| and data @>'{"device":{"platform": "android"}}' | |
| ) | |
| SELECT count(distinct a.clientId) | |
| FROM a | |
| INNER JOIN (select clientId, max(event_time) as ltime FROM a where event_time between '2018-03-14 08:00:00+00' AND '2018-03-14 20:10:00+00' GROUP BY clientId) as latest | |
| ON a.clientId = latest.clientId | |
| AND a.event_time = latest.ltime | |
| /* | |
| ^ Takes forever to run, doesn't use index either | |
| +-------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ | |
| | QUERY PLAN | | |
| |-------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | |
| | Aggregate (cost=21135.02..21135.03 rows=1 width=8) | | |
| | CTE a | | |
| | -> Seq Scan on mobileappmetrics (cost=0.00..21134.93 rows=1 width=89) | | |
| | Filter: ((data @> '{"app": {"appId": "com.example.someApp"}}'::jsonb) AND (data @> '{"device": {"platform": "android"}}'::jsonb)) | | |
| | -> Nested Loop (cost=0.03..0.09 rows=1 width=324) | | |
| | Join Filter: ((a.clientid = a_1.clientid) AND (a.event_time = (max(a_1.event_time)))) | | |
| | -> CTE Scan on a (cost=0.00..0.02 rows=1 width=332) | | |
| | -> HashAggregate (cost=0.03..0.04 rows=1 width=332) | | |
| | Group Key: a_1.clientid | | |
| | -> CTE Scan on a a_1 (cost=0.00..0.03 rows=1 width=332) | | |
| | Filter: ((event_time >= '2018-03-14 08:00:00+00'::timestamp with time zone) AND (event_time <= '2018-03-14 20:10:00+00'::timestamp with time zone)) | | |
| +-------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ | |
| */ | |
| -- CTE query on relational tables | |
| EXPLAIN ANALYZE WITH a as ( | |
| select app.clientId, app.event_time | |
| FROM mobilemetrics_app as app | |
| INNER JOIN mobilemetrics_device as device ON app.clientId = device.clientId | |
| WHERE app.app_id = 'com.example.someApp' | |
| AND device.platform = 'android' | |
| ) | |
| SELECT count(distinct a.clientId) | |
| FROM a | |
| INNER JOIN (select clientId, max(event_time) as ltime FROM a where event_time between '2018-03-14 08:00:00+00' AND '2018-03-14 20:10:00+00' GROUP BY clientId) as latest | |
| ON a.clientId = latest.clientId | |
| AND a.event_time = latest.ltime | |
| /* | |
| +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ | |
| | QUERY PLAN | | |
| |------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | |
| | Aggregate (cost=42207.46..42207.47 rows=1 width=8) (actual time=3586.511..3586.511 rows=1 loops=1) | | |
| | CTE a | | |
| | -> Hash Join (cost=10522.00..27194.09 rows=149970 width=89) (actual time=72.553..226.526 rows=150000 loops=1) | | |
| | Hash Cond: (app.clientid = device.clientid) | | |
| | -> Seq Scan on mobilemetrics_app app (cost=0.00..8165.00 rows=149970 width=89) (actual time=0.010..50.066 rows=150000 loops=1) | | |
| | Filter: ((event_time >= '2018-03-14 08:00:00+00'::timestamp with time zone) AND (event_time <= '2018-03-14 20:10:00+00'::timestamp with time zone) AND (app_id = 'com.example.someApp'::bpchar)) | | |
| | -> Hash (cost=6596.00..6596.00 rows=150000 width=81) (actual time=72.401..72.401 rows=150000 loops=1) | | |
| | Buckets: 32768 Batches: 8 Memory Usage: 2313kB | | |
| | -> Seq Scan on mobilemetrics_device device (cost=0.00..6596.00 rows=150000 width=81) (actual time=0.005..35.787 rows=150000 loops=1) | | |
| | Filter: (platform = 'android'::bpchar) | | |
| | -> Hash Join (cost=3756.25..15011.50 rows=750 width=324) (actual time=414.280..548.130 rows=150000 loops=1) | | |
| | Hash Cond: ((a.clientid = a_1.clientid) AND (a.event_time = (max(a_1.event_time)))) | | |
| | -> CTE Scan on a (cost=0.00..2999.40 rows=149970 width=332) (actual time=72.555..88.043 rows=150000 loops=1) | | |
| | -> Hash (cost=3753.25..3753.25 rows=200 width=332) (actual time=341.593..341.593 rows=150000 loops=1) | | |
| | Buckets: 32768 (originally 1024) Batches: 8 (originally 1) Memory Usage: 3841kB | | |
| | -> HashAggregate (cost=3749.25..3751.25 rows=200 width=332) (actual time=264.326..294.075 rows=150000 loops=1) | | |
| | Group Key: a_1.clientid | | |
| | -> CTE Scan on a a_1 (cost=0.00..2999.40 rows=149970 width=332) (actual time=0.001..199.939 rows=150000 loops=1) | | |
| | Planning time: 0.314 ms | | |
| | Execution time: 3589.985 ms | | |
| +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ | |
| Adding hash indexes on device.platform and app.appId made the query planner think it would be faster but was slower for some reason | |
| +-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ | |
| | QUERY PLAN | | |
| |-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | |
| | Aggregate (cost=2249.03..2249.04 rows=1 width=8) (actual time=6566.892..6566.893 rows=1 loops=1) | | |
| | CTE a | | |
| | -> Hash Join (cost=1101.74..2218.79 rows=573 width=332) (actual time=137.955..328.806 rows=150000 loops=1) | | |
| | Hash Cond: (device.clientid = app.clientid) | | |
| | -> Bitmap Heap Scan on mobilemetrics_device device (cost=22.74..1119.02 rows=354 width=324) (actual time=13.083..74.084 rows=150000 loops=1) | | |
| | Recheck Cond: (platform = 'android'::bpchar) | | |
| | Heap Blocks: exact=4716 | | |
| | -> Bitmap Index Scan on idx_device_platform (cost=0.00..22.66 rows=354 width=0) (actual time=12.557..12.557 rows=150000 loops=1) | | |
| | Index Cond: (platform = 'android'::bpchar) | | |
| | -> Hash (cost=1074.94..1074.94 rows=324 width=332) (actual time=124.760..124.760 rows=150000 loops=1) | | |
| | Buckets: 32768 (originally 1024) Batches: 8 (originally 1) Memory Usage: 3841kB | | |
| | -> Bitmap Heap Scan on mobilemetrics_app app (cost=26.51..1074.94 rows=324 width=332) (actual time=9.685..84.460 rows=150000 loops=1) | | |
| | Recheck Cond: (app_id = 'com.example.someApp'::bpchar) | | |
| | Heap Blocks: exact=5888 | | |
| | -> Bitmap Index Scan on idx_app_app_id (cost=0.00..26.43 rows=324 width=0) (actual time=9.046..9.046 rows=150000 loops=1) | | |
| | Index Cond: (app_id = 'com.example.someApp'::bpchar) | | |
| | -> Hash Join (cost=14.48..30.24 rows=1 width=324) (actual time=3461.678..3592.076 rows=150000 loops=1) | | |
| | Hash Cond: ((a.clientid = a_1.clientid) AND (a.event_time = (max(a_1.event_time)))) | | |
| | -> CTE Scan on a (cost=0.00..11.46 rows=573 width=332) (actual time=137.958..153.073 rows=150000 loops=1) | | |
| | -> Hash (cost=14.43..14.43 rows=3 width=332) (actual time=3323.597..3323.597 rows=150000 loops=1) | | |
| | Buckets: 32768 (originally 1024) Batches: 8 (originally 1) Memory Usage: 3841kB | | |
| | -> GroupAggregate (cost=14.35..14.40 rows=3 width=332) (actual time=2583.480..3277.213 rows=150000 loops=1) | | |
| | Group Key: a_1.clientid | | |
| | -> Sort (cost=14.35..14.36 rows=3 width=332) (actual time=2583.468..3231.360 rows=150000 loops=1) | | |
| | Sort Key: a_1.clientid | | |
| | Sort Method: external merge Disk: 15496kB | | |
| | -> CTE Scan on a a_1 (cost=0.00..14.33 rows=3 width=332) (actual time=0.004..251.680 rows=150000 loops=1) | | |
| | Filter: ((event_time >= '2018-03-14 08:00:00+00'::timestamp with time zone) AND (event_time <= '2018-03-14 20:10:00+00'::timestamp with time zone)) | | |
| | Planning time: 0.512 ms | | |
| | Execution time: 6571.462 ms | | |
| +-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ | |
| After re-ANALYZE-ing the indexed tables it reverted to a similar plan to the non-indexed one | |
| +-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ | |
| | QUERY PLAN | | |
| |-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | |
| | Aggregate (cost=34689.21..34689.22 rows=1 width=8) (actual time=3516.218..3516.218 rows=1 loops=1) | | |
| | CTE a | | |
| | -> Hash Join (cost=10522.00..26794.50 rows=150000 width=89) (actual time=68.865..215.899 rows=150000 loops=1) | | |
| | Hash Cond: (app.clientid = device.clientid) | | |
| | -> Seq Scan on mobilemetrics_app app (cost=0.00..7763.00 rows=150000 width=89) (actual time=0.016..40.896 rows=150000 loops=1) | | |
| | Filter: (app_id = 'com.example.someApp'::bpchar) | | |
| | -> Hash (cost=6596.00..6596.00 rows=150000 width=81) (actual time=68.705..68.705 rows=150000 loops=1) | | |
| | Buckets: 32768 Batches: 8 Memory Usage: 2314kB | | |
| | -> Seq Scan on mobilemetrics_device device (cost=0.00..6596.00 rows=150000 width=81) (actual time=0.009..30.588 rows=150000 loops=1) | | |
| | Filter: (platform = 'android'::bpchar) | | |
| | -> Hash Join (cost=3760.57..7892.89 rows=731 width=324) (actual time=416.927..547.799 rows=150000 loops=1) | | |
| | Hash Cond: ((a.clientid = a_1.clientid) AND (a.event_time = (max(a_1.event_time)))) | | |
| | -> CTE Scan on a (cost=0.00..3000.00 rows=150000 width=332) (actual time=68.868..83.907 rows=150000 loops=1) | | |
| | -> Hash (cost=3757.65..3757.65 rows=195 width=332) (actual time=347.977..347.977 rows=150000 loops=1) | | |
| | Buckets: 32768 (originally 1024) Batches: 8 (originally 1) Memory Usage: 3841kB | | |
| | -> HashAggregate (cost=3753.75..3755.70 rows=195 width=332) (actual time=269.337..299.492 rows=150000 loops=1) | | |
| | Group Key: a_1.clientid | | |
| | -> CTE Scan on a a_1 (cost=0.00..3750.00 rows=750 width=332) (actual time=0.004..206.685 rows=150000 loops=1) | | |
| | Filter: ((event_time >= '2018-03-14 08:00:00+00'::timestamp with time zone) AND (event_time <= '2018-03-14 20:10:00+00'::timestamp with time zone)) | | |
| | Planning time: 1.060 ms | | |
| | Execution time: 3520.005 ms | | |
| +-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ | |
| */ |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment