PostgreSQL 9 - Hagander · PDF filePostgreSQL 9.5 Postgres Open 2015 Dallas, TX Magnus...
Transcript of PostgreSQL 9 - Hagander · PDF filePostgreSQL 9.5 Postgres Open 2015 Dallas, TX Magnus...
![Page 2: PostgreSQL 9 - Hagander · PDF filePostgreSQL 9.5 Postgres Open 2015 Dallas, TX Magnus Hagander magnus@](https://reader030.fdocuments.us/reader030/viewer/2022021501/5a78f0557f8b9a5a148dfaa0/html5/thumbnails/2.jpg)
Magnus Hagander•PostgreSQL
•Core Team member•Committer•PostgreSQL Europe
•Redpill Linpro•Infrastructure services•Principal database consultant
![Page 3: PostgreSQL 9 - Hagander · PDF filePostgreSQL 9.5 Postgres Open 2015 Dallas, TX Magnus Hagander magnus@](https://reader030.fdocuments.us/reader030/viewer/2022021501/5a78f0557f8b9a5a148dfaa0/html5/thumbnails/3.jpg)
Do you read...•planet.postgresql.org
![Page 4: PostgreSQL 9 - Hagander · PDF filePostgreSQL 9.5 Postgres Open 2015 Dallas, TX Magnus Hagander magnus@](https://reader030.fdocuments.us/reader030/viewer/2022021501/5a78f0557f8b9a5a148dfaa0/html5/thumbnails/4.jpg)
Development schedule•June 10, 2014 - branch 9.4•June 2014 - CF1•August 2014 - CF2•October 2014 - CF3•December 2014 - CF4•February 2015 - CF5•August 2015 - Alpha2!
![Page 5: PostgreSQL 9 - Hagander · PDF filePostgreSQL 9.5 Postgres Open 2015 Dallas, TX Magnus Hagander magnus@](https://reader030.fdocuments.us/reader030/viewer/2022021501/5a78f0557f8b9a5a148dfaa0/html5/thumbnails/5.jpg)
Current status•Alpha 2 has been released•Please help with review and testing!•Packages now available!
![Page 6: PostgreSQL 9 - Hagander · PDF filePostgreSQL 9.5 Postgres Open 2015 Dallas, TX Magnus Hagander magnus@](https://reader030.fdocuments.us/reader030/viewer/2022021501/5a78f0557f8b9a5a148dfaa0/html5/thumbnails/6.jpg)
Current status•Statistics!
•2597 files changed•215199 insertions (+)•220459 deletions(-)
•Almost double that of 9.4!•But..?
![Page 7: PostgreSQL 9 - Hagander · PDF filePostgreSQL 9.5 Postgres Open 2015 Dallas, TX Magnus Hagander magnus@](https://reader030.fdocuments.us/reader030/viewer/2022021501/5a78f0557f8b9a5a148dfaa0/html5/thumbnails/7.jpg)
So what's really new•Developer and SQL features•DBA and administration•Performance
![Page 8: PostgreSQL 9 - Hagander · PDF filePostgreSQL 9.5 Postgres Open 2015 Dallas, TX Magnus Hagander magnus@](https://reader030.fdocuments.us/reader030/viewer/2022021501/5a78f0557f8b9a5a148dfaa0/html5/thumbnails/8.jpg)
New features•Developer and SQL features•DBA and administration•Performance
![Page 9: PostgreSQL 9 - Hagander · PDF filePostgreSQL 9.5 Postgres Open 2015 Dallas, TX Magnus Hagander magnus@](https://reader030.fdocuments.us/reader030/viewer/2022021501/5a78f0557f8b9a5a148dfaa0/html5/thumbnails/9.jpg)
Multi-column subselectUPDATE
•Update more than one column with subselect•SQL standard syntaxUPDATE tab SET (col1, col2) = (SELECT foo, bar FROM tab2) WHERE ...
![Page 10: PostgreSQL 9 - Hagander · PDF filePostgreSQL 9.5 Postgres Open 2015 Dallas, TX Magnus Hagander magnus@](https://reader030.fdocuments.us/reader030/viewer/2022021501/5a78f0557f8b9a5a148dfaa0/html5/thumbnails/10.jpg)
Numeric generate_series•Previously "only" integer
•And timestamps•Now decimals and bigger numberspostgres=# SELECT * FROM generate_series(0, 1, 0.1); generate_series----------------- 0 0.1 0.2 0.3
![Page 11: PostgreSQL 9 - Hagander · PDF filePostgreSQL 9.5 Postgres Open 2015 Dallas, TX Magnus Hagander magnus@](https://reader030.fdocuments.us/reader030/viewer/2022021501/5a78f0557f8b9a5a148dfaa0/html5/thumbnails/11.jpg)
SKIP LOCKED•Like SELECT NOWAIT•Except skip rows instead of errorpostgres=# SELECT * FROM a FOR UPDATE NOWAIT;ERROR: could not obtain lock on row in relation "a"postgres=# SELECT * FROM a FOR UPDATE SKIP LOCKED; a | b | c----+----+---- 2 | 2 | 2 3 | 3 | 3
![Page 12: PostgreSQL 9 - Hagander · PDF filePostgreSQL 9.5 Postgres Open 2015 Dallas, TX Magnus Hagander magnus@](https://reader030.fdocuments.us/reader030/viewer/2022021501/5a78f0557f8b9a5a148dfaa0/html5/thumbnails/12.jpg)
Row level security•Apply access policies per row•Limit access to individual rows
•On top of tables and columns•Regular ACLs still apply
•Superusers and owners bypass•And BYPASSRLS roles
![Page 13: PostgreSQL 9 - Hagander · PDF filePostgreSQL 9.5 Postgres Open 2015 Dallas, TX Magnus Hagander magnus@](https://reader030.fdocuments.us/reader030/viewer/2022021501/5a78f0557f8b9a5a148dfaa0/html5/thumbnails/13.jpg)
Row level securitypostgres=# ALTER TABLE companies ENABLE ROW LEVEL SECURITY;ALTER TABLE
postgres=# CREATE POLICY companies_managerpostgres-# ON companiespostgres-# FOR ALLpostgres-# TO publicpostgres-# USING (manager = CURRENT_USER);CREATE POLICY
![Page 14: PostgreSQL 9 - Hagander · PDF filePostgreSQL 9.5 Postgres Open 2015 Dallas, TX Magnus Hagander magnus@](https://reader030.fdocuments.us/reader030/viewer/2022021501/5a78f0557f8b9a5a148dfaa0/html5/thumbnails/14.jpg)
Row level securitypostgres=# SELECT * FROM companies; manager | company---------+---------- mha | Company1 mha | Company2 test | Company3postgres=# \c postgres testYou are now connected to database "postgres" as user "test".postgres=> select * from companies; manager | company---------+---------- test | Company3
![Page 15: PostgreSQL 9 - Hagander · PDF filePostgreSQL 9.5 Postgres Open 2015 Dallas, TX Magnus Hagander magnus@](https://reader030.fdocuments.us/reader030/viewer/2022021501/5a78f0557f8b9a5a148dfaa0/html5/thumbnails/15.jpg)
Row level security•Policies on any "regular" expression
•No aggregates!•But quite complicated
•Multiple policies can be defined per table•Results are ORed
•Does not affect cascading RI operations
![Page 16: PostgreSQL 9 - Hagander · PDF filePostgreSQL 9.5 Postgres Open 2015 Dallas, TX Magnus Hagander magnus@](https://reader030.fdocuments.us/reader030/viewer/2022021501/5a78f0557f8b9a5a148dfaa0/html5/thumbnails/16.jpg)
Row level securityCREATE POLICY companies_manager_rON companiesUSING (manager IN ( WITH RECURSIVE t AS ( SELECT person,manager FROM managers WHERE manager=CURRENT_USER UNION ALL SELECT m.person, m.manager FROM managers m INNER JOIN t ON t.person=m.manager ) SELECT person FROM t))
![Page 17: PostgreSQL 9 - Hagander · PDF filePostgreSQL 9.5 Postgres Open 2015 Dallas, TX Magnus Hagander magnus@](https://reader030.fdocuments.us/reader030/viewer/2022021501/5a78f0557f8b9a5a148dfaa0/html5/thumbnails/17.jpg)
INSERT ... ON CONFLICT•INSERT ... ON CONFLICT DO {UPDATE | IGNORE}•aka UPSERT•Similar to MERGE
•Except better (in some ways)!•Based on "speculative insertion"
![Page 18: PostgreSQL 9 - Hagander · PDF filePostgreSQL 9.5 Postgres Open 2015 Dallas, TX Magnus Hagander magnus@](https://reader030.fdocuments.us/reader030/viewer/2022021501/5a78f0557f8b9a5a148dfaa0/html5/thumbnails/18.jpg)
INSERT ... ON CONFLICTINSERT INTO test (id, t)VALUES (2, 'foobar')ON CONFLICTDO NOTHING
![Page 19: PostgreSQL 9 - Hagander · PDF filePostgreSQL 9.5 Postgres Open 2015 Dallas, TX Magnus Hagander magnus@](https://reader030.fdocuments.us/reader030/viewer/2022021501/5a78f0557f8b9a5a148dfaa0/html5/thumbnails/19.jpg)
INSERT ... ON CONFLICTINSERT INTO test (id, t)VALUES (2, 'foobar')ON CONFLICT(id) DOUPDATE SET t=excluded.t
![Page 20: PostgreSQL 9 - Hagander · PDF filePostgreSQL 9.5 Postgres Open 2015 Dallas, TX Magnus Hagander magnus@](https://reader030.fdocuments.us/reader030/viewer/2022021501/5a78f0557f8b9a5a148dfaa0/html5/thumbnails/20.jpg)
INSERT ... ON CONFLICTINSERT INTO counters(url, num)VALUES ('/some/where', 1)ON CONFLICT(url) DOUPDATE SET num=counters.num+excluded.num
![Page 21: PostgreSQL 9 - Hagander · PDF filePostgreSQL 9.5 Postgres Open 2015 Dallas, TX Magnus Hagander magnus@](https://reader030.fdocuments.us/reader030/viewer/2022021501/5a78f0557f8b9a5a148dfaa0/html5/thumbnails/21.jpg)
GROUPING SETS•CUBE and ROLLUP
•But also fully generic•"Super-aggregates"•Partial sums etc
![Page 22: PostgreSQL 9 - Hagander · PDF filePostgreSQL 9.5 Postgres Open 2015 Dallas, TX Magnus Hagander magnus@](https://reader030.fdocuments.us/reader030/viewer/2022021501/5a78f0557f8b9a5a148dfaa0/html5/thumbnails/22.jpg)
GROUPING SETSpostgres=# SELECT dept, count(*) FROM empspostgres-# GROUP BY ROLLUP(dept); dept | count-------+------- it | 3 sales | 2 | 5
![Page 23: PostgreSQL 9 - Hagander · PDF filePostgreSQL 9.5 Postgres Open 2015 Dallas, TX Magnus Hagander magnus@](https://reader030.fdocuments.us/reader030/viewer/2022021501/5a78f0557f8b9a5a148dfaa0/html5/thumbnails/23.jpg)
GROUPING SETSpostgres=# SELECT dept, name, count(*), sum(payout)postgres-# FROM payouts GROUP BY ROLLUP(dept, name); dept | name | count | sum-------+-------+-------+------ it | Eva | 3 | 400 it | Johan | 2 | 350 it | Olle | 1 | 200 it | | 6 | 950 sales | Erik | 1 | 120 sales | Lisa | 2 | 220 sales | | 3 | 340 | | 9 | 1290
![Page 24: PostgreSQL 9 - Hagander · PDF filePostgreSQL 9.5 Postgres Open 2015 Dallas, TX Magnus Hagander magnus@](https://reader030.fdocuments.us/reader030/viewer/2022021501/5a78f0557f8b9a5a148dfaa0/html5/thumbnails/24.jpg)
New features•Developer and SQL features•DBA and administration•Performance
![Page 25: PostgreSQL 9 - Hagander · PDF filePostgreSQL 9.5 Postgres Open 2015 Dallas, TX Magnus Hagander magnus@](https://reader030.fdocuments.us/reader030/viewer/2022021501/5a78f0557f8b9a5a148dfaa0/html5/thumbnails/25.jpg)
cluster_name•New GUC•Included in process title•For multi-instance deployments31589 ? Ss 0:00 postgres: mytestcluster: logger process31591 ? Ss 0:00 postgres: mytestcluster: checkpointer process
![Page 26: PostgreSQL 9 - Hagander · PDF filePostgreSQL 9.5 Postgres Open 2015 Dallas, TX Magnus Hagander magnus@](https://reader030.fdocuments.us/reader030/viewer/2022021501/5a78f0557f8b9a5a148dfaa0/html5/thumbnails/26.jpg)
IMPORT FOREIGN SCHEMA•Import complete schema through FDW•No need to manually create tablespostgres=# CREATE SCHEMA remoteschema;CREATE SCHEMApostgres=# IMPORT FOREIGN SCHEMA testschema FROM SERVER otherserver INTO remoteschema;IMPORT FOREIGN SCHEMApostgres=# \det remoteschema.* List of foreign tables Schema | Table | Server--------------+-------+------------- remoteschema | test2 | otherserver remoteschema | test3 | otherserver(1 row)
![Page 27: PostgreSQL 9 - Hagander · PDF filePostgreSQL 9.5 Postgres Open 2015 Dallas, TX Magnus Hagander magnus@](https://reader030.fdocuments.us/reader030/viewer/2022021501/5a78f0557f8b9a5a148dfaa0/html5/thumbnails/27.jpg)
Foreign table inheritance•Foreign tables can be in inheritance trees•Which is used for partitioning•Can be used for sharding
![Page 28: PostgreSQL 9 - Hagander · PDF filePostgreSQL 9.5 Postgres Open 2015 Dallas, TX Magnus Hagander magnus@](https://reader030.fdocuments.us/reader030/viewer/2022021501/5a78f0557f8b9a5a148dfaa0/html5/thumbnails/28.jpg)
SET UNLOGGED•Unlogged table property can be turned on and off•Simple ALTER statementpostgres=# ALTER TABLE a SET UNLOGGED;ALTER TABLEpostgres=# ALTER TABLE a SET LOGGED;ALTER TABLE
![Page 29: PostgreSQL 9 - Hagander · PDF filePostgreSQL 9.5 Postgres Open 2015 Dallas, TX Magnus Hagander magnus@](https://reader030.fdocuments.us/reader030/viewer/2022021501/5a78f0557f8b9a5a148dfaa0/html5/thumbnails/29.jpg)
ALTER SYSTEM RESET•Reset config variable back to
•postgresql.conf•default value
•Removes from postgresql.auto.conf filepostgres=# ALTER SYSTEM RESET work_mem;ALTER SYSTEMpostgres=# SELECT pg_reload_conf();
![Page 30: PostgreSQL 9 - Hagander · PDF filePostgreSQL 9.5 Postgres Open 2015 Dallas, TX Magnus Hagander magnus@](https://reader030.fdocuments.us/reader030/viewer/2022021501/5a78f0557f8b9a5a148dfaa0/html5/thumbnails/30.jpg)
commit timestamp tracking•Optional tracking of commit timestamps
•track_commit_timestamp=on•See when a row was committed etc?postgres=# SELECT xmin, pg_xact_commit_timestamp(xmin) FROM a; xmin | pg_xact_commit_timestamp------+------------------------------- 787 | 2015-03-15 15:09:52.253007+00
postgres=# SELECT * FROM pg_last_committed_xact(); xid | timestamp-----+------------------------------- 791 | 2015-03-15 15:11:38.709125+00
![Page 31: PostgreSQL 9 - Hagander · PDF filePostgreSQL 9.5 Postgres Open 2015 Dallas, TX Magnus Hagander magnus@](https://reader030.fdocuments.us/reader030/viewer/2022021501/5a78f0557f8b9a5a148dfaa0/html5/thumbnails/31.jpg)
min and max wal size•checkpoint_segments removed!•Instead, control min and max size
•min_wal_size (default 80MB)•max_wal_size (default 1GB)
•Checkpoints auto-tuned to happen in between•Moving average of previous checkpoints
•Space only consumed when actually needed
![Page 32: PostgreSQL 9 - Hagander · PDF filePostgreSQL 9.5 Postgres Open 2015 Dallas, TX Magnus Hagander magnus@](https://reader030.fdocuments.us/reader030/viewer/2022021501/5a78f0557f8b9a5a148dfaa0/html5/thumbnails/32.jpg)
recovery_target_action•What happens when recovery completes
•pause•promote•shutdown
•Replaces pause_at_recovery_target
![Page 33: PostgreSQL 9 - Hagander · PDF filePostgreSQL 9.5 Postgres Open 2015 Dallas, TX Magnus Hagander magnus@](https://reader030.fdocuments.us/reader030/viewer/2022021501/5a78f0557f8b9a5a148dfaa0/html5/thumbnails/33.jpg)
pg_rewind•Ability to rewind WAL on old master•Re-use former master without rebuild
![Page 34: PostgreSQL 9 - Hagander · PDF filePostgreSQL 9.5 Postgres Open 2015 Dallas, TX Magnus Hagander magnus@](https://reader030.fdocuments.us/reader030/viewer/2022021501/5a78f0557f8b9a5a148dfaa0/html5/thumbnails/34.jpg)
SSL code refactoring•OpenSSL independence•Though only OpenSSL supported so far...•Add support for Subject Alternate Name
![Page 35: PostgreSQL 9 - Hagander · PDF filePostgreSQL 9.5 Postgres Open 2015 Dallas, TX Magnus Hagander magnus@](https://reader030.fdocuments.us/reader030/viewer/2022021501/5a78f0557f8b9a5a148dfaa0/html5/thumbnails/35.jpg)
pg_stat_ssl•View status of existing SSL connection•Mostly same info as contrib/sslinfo•But for all connections
![Page 36: PostgreSQL 9 - Hagander · PDF filePostgreSQL 9.5 Postgres Open 2015 Dallas, TX Magnus Hagander magnus@](https://reader030.fdocuments.us/reader030/viewer/2022021501/5a78f0557f8b9a5a148dfaa0/html5/thumbnails/36.jpg)
pg_stat_statements•New values for execution times
•Max•Min•Mean•Stddev
![Page 37: PostgreSQL 9 - Hagander · PDF filePostgreSQL 9.5 Postgres Open 2015 Dallas, TX Magnus Hagander magnus@](https://reader030.fdocuments.us/reader030/viewer/2022021501/5a78f0557f8b9a5a148dfaa0/html5/thumbnails/37.jpg)
pg_xlogdump•Now takes --stats argument•Find out what takes space in the xlog•(and of course look at details like before)
![Page 38: PostgreSQL 9 - Hagander · PDF filePostgreSQL 9.5 Postgres Open 2015 Dallas, TX Magnus Hagander magnus@](https://reader030.fdocuments.us/reader030/viewer/2022021501/5a78f0557f8b9a5a148dfaa0/html5/thumbnails/38.jpg)
New features•Developer and SQL features•DBA and administration•Performance
![Page 39: PostgreSQL 9 - Hagander · PDF filePostgreSQL 9.5 Postgres Open 2015 Dallas, TX Magnus Hagander magnus@](https://reader030.fdocuments.us/reader030/viewer/2022021501/5a78f0557f8b9a5a148dfaa0/html5/thumbnails/39.jpg)
BRIN indexes•Block Range Index
•Formerly known as MinMax•But supports other opclasses too
•Very small indexes•Stores only bounds-per-block-range
•Default is 128 blocks•Scans all blocks for matches•Best suited for naturally ordered tables
![Page 40: PostgreSQL 9 - Hagander · PDF filePostgreSQL 9.5 Postgres Open 2015 Dallas, TX Magnus Hagander magnus@](https://reader030.fdocuments.us/reader030/viewer/2022021501/5a78f0557f8b9a5a148dfaa0/html5/thumbnails/40.jpg)
BRIN indexespostgres=# CREATE INDEX a_brin ON a USING BRIN(a);CREATE INDEXpostgres=# EXPLAIN SELECT * FROM a WHERE a=3; QUERY PLAN---------------------------------------------------------------------- Bitmap Heap Scan on a (cost=12.01..16.02 rows=1 width=12) Recheck Cond: (a = 3) -> Bitmap Index Scan on a_brin (cost=0.00..12.01 rows=1 width=0) Index Cond: (a = 3)
postgres=# CREATE INDEX a_brin_b ON apostgres-# USING BRIN(b) WITH (pages_per_range=1024);CREATE INDEX
![Page 41: PostgreSQL 9 - Hagander · PDF filePostgreSQL 9.5 Postgres Open 2015 Dallas, TX Magnus Hagander magnus@](https://reader030.fdocuments.us/reader030/viewer/2022021501/5a78f0557f8b9a5a148dfaa0/html5/thumbnails/41.jpg)
GIN pending list•Max size of GIN pending list configurable
•Used for GIN fast update•Control how often cleanup happens•Prefer VACUUM
•Previously controlled by work_mem•Now gin_pending_list_limit
•Both GUC and storage parameter
![Page 42: PostgreSQL 9 - Hagander · PDF filePostgreSQL 9.5 Postgres Open 2015 Dallas, TX Magnus Hagander magnus@](https://reader030.fdocuments.us/reader030/viewer/2022021501/5a78f0557f8b9a5a148dfaa0/html5/thumbnails/42.jpg)
GiST index only scan•Index only scan for GiST indexes•Most, but not all, opclasses
![Page 43: PostgreSQL 9 - Hagander · PDF filePostgreSQL 9.5 Postgres Open 2015 Dallas, TX Magnus Hagander magnus@](https://reader030.fdocuments.us/reader030/viewer/2022021501/5a78f0557f8b9a5a148dfaa0/html5/thumbnails/43.jpg)
WAL compression•Support for compressing full page images•Smaller WAL
•Faster writes, faster replication•Costs CPU
•Only compresses FPIs•Still useful to gzip archives!
•Also new WAL format and CRC
![Page 44: PostgreSQL 9 - Hagander · PDF filePostgreSQL 9.5 Postgres Open 2015 Dallas, TX Magnus Hagander magnus@](https://reader030.fdocuments.us/reader030/viewer/2022021501/5a78f0557f8b9a5a148dfaa0/html5/thumbnails/44.jpg)
Sorting enhancements•Abbreviated keys for sorting
•text•numeric
•Pre-check for equality•memcmp is fast!
•more...
![Page 45: PostgreSQL 9 - Hagander · PDF filePostgreSQL 9.5 Postgres Open 2015 Dallas, TX Magnus Hagander magnus@](https://reader030.fdocuments.us/reader030/viewer/2022021501/5a78f0557f8b9a5a148dfaa0/html5/thumbnails/45.jpg)
Locking enhancements•Internal atomic operations API•lwlock scalability increased using this•Many more lockless operations
•E.g. triggers and foreign keys•etc.
![Page 46: PostgreSQL 9 - Hagander · PDF filePostgreSQL 9.5 Postgres Open 2015 Dallas, TX Magnus Hagander magnus@](https://reader030.fdocuments.us/reader030/viewer/2022021501/5a78f0557f8b9a5a148dfaa0/html5/thumbnails/46.jpg)
There's always more
![Page 47: PostgreSQL 9 - Hagander · PDF filePostgreSQL 9.5 Postgres Open 2015 Dallas, TX Magnus Hagander magnus@](https://reader030.fdocuments.us/reader030/viewer/2022021501/5a78f0557f8b9a5a148dfaa0/html5/thumbnails/47.jpg)
There's always more•Lots of smaller fixes•Performance improvements•etc, etc•Can't mention them all!
![Page 48: PostgreSQL 9 - Hagander · PDF filePostgreSQL 9.5 Postgres Open 2015 Dallas, TX Magnus Hagander magnus@](https://reader030.fdocuments.us/reader030/viewer/2022021501/5a78f0557f8b9a5a148dfaa0/html5/thumbnails/48.jpg)
Tiny favorite?•psql detects if sent a custom format dump•We all did this:mha@mha-laptop:~$ 9.4/bin/psql -f /tmp/custom.dump postgrespsql:/tmp/custom.dump:1: ERROR: syntax error at or near "PGDMP"LINE 1: PGDMP�
•Now:mha@mha-laptop:~$ head/bin/psql -f /tmp/custom.dump postgresThe input is a PostgreSQL custom-format dump.Use the pg_restore command-line client to restore this dump to a database.
![Page 49: PostgreSQL 9 - Hagander · PDF filePostgreSQL 9.5 Postgres Open 2015 Dallas, TX Magnus Hagander magnus@](https://reader030.fdocuments.us/reader030/viewer/2022021501/5a78f0557f8b9a5a148dfaa0/html5/thumbnails/49.jpg)
What's your biggestfeature?
•UPSERT?•GROUPING SETS?•RLS?•Foreign Table Inheritance?•BRIN?•Other?
![Page 50: PostgreSQL 9 - Hagander · PDF filePostgreSQL 9.5 Postgres Open 2015 Dallas, TX Magnus Hagander magnus@](https://reader030.fdocuments.us/reader030/viewer/2022021501/5a78f0557f8b9a5a148dfaa0/html5/thumbnails/50.jpg)
Thank you!Magnus Hagander
[email protected]@magnushagander
http://www.hagander.net/talks/
This material is licensed CC BY-NC 4.0.