PostgreSQL 11: something for everyone
lwn.net
lwn.net
"It's clear to me that a certain proportion of application developers are allergic to putting business logic in the database system, despite the fact that batch processing logic is often a great deal faster when implemented that way. Perhaps the fact that clients are now able to cede full control of transaction scope to procedural code running on the database server (code that can be written in a familiar scripting language) will help application developers to get over the understandable aversion. Hope springs eternal."
From coding in Python with Django, I'd need to give up version control as I know it, use a different Python environment, give up unit testing as I know it (and as it integrates with the rest of my application), authentication (because that's owned by Django), distributed tasks and queueing, performance monitoring (as I know it), etc.
I would really like to see a Proof of Concept of a modern web framework like Django/Rails, but with a strong focus on moving as much to the database as possible - user management, queues, etc, as I think there are many benefits to be had from pushing more into the database.
Until something like that exists though, I think moving logic to the database will lose development tooling and process that we are used to, and end up re-implementing logic in multiple places which isn't great.
OLTP transactions for running a basic webapp don't need to move there, and the data still needs to be retrieve anyway, but things like a queue, the actual push and pop functions can easily be wrapped in SQL functions that make it easy and safe to call from multiple apps.
Debugging can be a little harder, but testing generally isn't you know the calling signature(s) PG uses so writing a class for UnitTest/pytest is straightforward.
Our APP logins ARE DB logins for our desktop product(s), but for web we have a single DB user that web apps log into the DB as. We'd love for web logins to become PG logins as well, but we haven't found a very performant way of doing that yet. PG sessions are a heavy startup cost.
The functionality for us, isn't the same between the web and desktop product(s) which helps a lot.
We push access control into the DB for the most part, using row, column and table level access controls PG provides us. So our desktop product has basically no access controls in it, since the DB provides all of that at login time. We have a few power users that connect directly with SQL, and we have zero issues just handing them access, since the DB controls what they can/can not see just fine.
Sadly so many tools just assume their DB connection is the equivalent to root, and can just go do whatever it wants, which makes lots of tools, especially reporting tools, hard to just hand directly to end users.
Have you transitioned your business logic into your DB from your app or is that approach something you started with?
We looked into doing something similar, but it didn't scale to our workload and had a ton of edge cases we kept getting caught up in. If a breach on the PG connection happens with that approach, the world gets super bad, super fast, as if I remember right, you need to be a super user to switch roles like that. That's very, very worrisome exposing super-user credentials like that to a website. If I remember right, we had to throw the connection away after using it for some reason, so the startup-cost gets cut some, but if you have a mass influx of users and your pool size is not gigantic, it gets painful waiting for new PG connections to come up. Plus the cost of holding open so many PG connections if your pool size is too large.... And our product has a module where all users attack it pretty much all at once, twice a day... this was back in the 8.X series of PG, so maybe it's a lot better now...
If postgrest manages to make it scale to our workload and took care of all the edge cases well, it would be super awesome. I'll have to check into how they handle that.. maybe it's something we can look into adding in the future, thanks!
No need for superuser, only membership to the role that it's going to switch is required. i.e. a `GRANT web_user TO connector`. Besides that, only privilege the connector role requires is LOGIN.
Loads of GUI applications write their UIs in application code. Eventually they have to reach to an actual graphics buffer, or OpenGL or similar lib, but only in the most simplistic, elementary way possible. (Sort of like `SELECT * FROM user WHERE id = 4`.)
That keeps 98% your logic in one place, with one language, one composition paradigm, and one set of tools.
For programs that have demanding graphics needs, however, they will start writing greater and greater amounts of code into shaders, to be run on the GPU. It's more complex to split logic like this, and the capabilities of shader languages aware far beneath those of general purpose programming languages. But when it matters, you'll happily take the improved performance.
It's great to see PostgreSQL doing what it can to close the gap and make this easier.
tl;dr
SQL queries are the graphics shaders of server programs.
Some of our customers do today very much leverage the parallelism Citus brings, but again that's across multiple nodes. A number of our customers don't choose Citus at all for real-time analytics, instead they're more focused on a more standard OLTP application, often multi-tenant app, that they need to scale.
Since I switched I don't use anything else
> "Throwaway accounts are ok for sensitive information, but please don't create them routinely. On HN, users should have an identity that others can relate to."
https://news.ycombinator.com/newsguidelines.html
As recently as a week ago, 'dang had this to say about throwaways:
> "HN can't be a community without members. Disembodied comments are not community members. No one is required to use their real name here, but users need to have some consistent identity for others to relate to. Otherwise we might as well have no usernames and no community at all."
https://news.ycombinator.com/item?id=17996295
And later goes on to make clear this includes "users who create throwaway accounts for everything, including uncontroversial stuff".
https://news.ycombinator.com/item?id=17996858
They also don't see or act on everything they see, so in that sense, I think you could say they "don't seem bothered." That said, I think it's fair to say it's preferred that you maintain some sort of identity.
The desire to create a community chafes against the zero verfication HN does for new accounts. If you want to create a community it should be harder to create a new account than login, but it's actually the opposite. To create a new account I don't need to remember my password, I just need a username and keyspam.
This probably isn't what Dang wants but it's how the site currently works. If it's easier to create an account a couple times a year than remember my login, especially when nobody is gonna remember someone who posts 4 times a year, what is the point of maintaining an account. Just being logical here.
The only thing I haven't been able to find a great solution for yet is comparing schemas between DB instances and generating DDL to make up the difference.
Have you looked at liquibase diff[0] before?
I created yet another python wrapper[1] for the jar to integrate it into my workflow for easy DDL generation as I need to compare databases.
Ultimately, yeah, I just got better at keeping track of the changes myself as in the article you link in your GitHub README: http://www.liquibase.org/2007/06/the-problem-with-database-d...
If you want something that's a webapp-style, perhaps to host as a separate service, then check out OmniDB from 2ndQuadrant. It's still maturing but has some unique management features that are helpful for Postgres.
[1]https://www.sql-workbench.eu/ [2]http://www.squirrelsql.org/
Advanced Query Tool is also good but it's geared more for BA than DBA.
Aqua Data Studio is also very good but too expensive ($499 single user licence).
For reference, I've also used pgadmin, DBeaver, DataGrip, SQuirrel in my two year long quest to find the best tool. I was assigned to pick best tool for 500 developers and business analysts.
What other features should I learn about (comparisons to MySQL/Oracle)? Any reason to focus on Postgres/Greenplum over a Hadoop hive/impala setup?
As for features to look in too, I'd look at the in-server functions (plpgsql, plv8, plperl etc) for extensibility. Custom and complex types are pretty awesome as well. Also the range of functionality in window functions can make some really interesting queries possible. Also the concurrency model does need some understanding, as it means a commit will fail rather than deadlock.
Why should use use PostgreSQL over Hadoop? If you have less than ~20TB of data then it's much less complex to set up and maintain, and as long as you don't want to read the entire data set at once it's fast. If you have more but it's easily partitioned (eg multi-tenant) then Citus have a package that can help, or if you have column-oriented data Citus have a different extension to help.
Postgres is also really well tested and has decades of production use behind it, so backups and restores are well known, though that does require manual scripting to copy the files.
Also for how extensible they are. I'm a complete amateur at GIS, but PostGIS is one of the neatest large-scale uses of the system I've seen.
> The v10 improvement essentially flattened the representation of expression state and changed how expression evaluation was performed.
It sounds like they were using an expression tree and changed it to something like truth tables, which are essentially "flat" expression representations that can support any boolean expression.
[1] https://www.postgresql.org/message-id/E1crtuL-0002bw-Pq%40ge...
I don't think that's an accurate description - it's more that we converted the tree representation (which still exists at parse/plan time) into an opcode/bytecode representation, evaluated by a mini-VM. Obviously that VM is quite specialized, and not general purpose (and currently it's afaik no internally turing complete, due to the lack of backward jumps, but that's just a question of the program generator, rather than the VM itself). The VM uses computed goto based dispatch if available (even though on modern x86 hardware the benefits aren't as large as on older HW).
My day job involves working on a query engine that faces some of the same design challenges, so I find this stuff very interesting.
That's why I did it, and why that's the first part in PG 11 that got JITed ;)
> My day job involves working on a query engine that faces some of the same design challenges, so I find this stuff very interesting.
FWIW, part of the design decisions I made for this stuff in PG were made to a significant part because it had to be adaptable incrementally without breaking too many idiosyncrasies (although there were some, in another commit, around multiple set-returning-functions in the SELECT list). I'd definitely do some things differently if I were to design more on a green field.
Covering indexes is tricky in Postgres, due to its MVCC implementation. Versioning info is in the table, not the index, so the index may have stale data. The table itself has to be consulted to find out if the data in the index is actually visible to your transaction.
INCLUDE/covering indexes are indexes that can include extra "payload" columns, and exist only for the benefit of index-only scans. You can add something extra to the primary key for the largest table in your database, all without changing how and when duplicate violation errors are raised.
CREATE TABLE large(id serial not null, tz timestamptz DEFAULT clock_timestamp() NOT NULL, data_double float8 DEFAULT random() NOT NULL, data_text text DEFAULT (random()::text) NOT NULL, data_other text NOT NULL);
INSERT INTO large(data_other) SELECT generate_series(1, 100000000) i;
SELECT pg_prewarm('large');
yields (best of three) postgres[23419][1]=# set jit = 1;
postgres[23419][1]=# EXPLAIN ANALYZE SELECT min(tz), sum(data_double), max(data_double) FROM large WHERE tz > '2018-09-23 14:47:30.298302-07';
┌───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
│ QUERY PLAN │
├───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┤
│ Finalize Aggregate (cost=1495625.42..1495625.43 rows=1 width=24) (actual time=3871.540..3871.540 rows=1 loops=1) │
│ -> Gather (cost=1495625.20..1495625.41 rows=2 width=24) (actual time=3871.388..3873.705 rows=3 loops=1) │
│ Workers Planned: 2 │
│ Workers Launched: 2 │
│ -> Partial Aggregate (cost=1494625.20..1494625.21 rows=1 width=24) (actual time=3856.050..3856.050 rows=1 loops=3) │
│ -> Parallel Seq Scan on large (cost=0.00..1417317.00 rows=10307760 width=16) (actual time=98.533..2216.673 rows=33333333 loops=3) │
│ Filter: (tz > '2018-09-23 14:47:30.298302-07'::timestamp with time zone) │
│ Planning Time: 0.041 ms │
│ JIT: │
│ Functions: 8 │
│ Generation Time: 0.934 ms │
│ Inlining: true │
│ Inlining Time: 5.145 ms │
│ Optimization: true │
│ Optimization Time: 55.719 ms │
│ Emission Time: 43.121 ms │
│ Execution Time: 3874.702 ms │
└───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
(17 rows)
postgres[23419][1]=# set jit = 0;
postgres[23419][1]=# EXPLAIN ANALYZE SELECT min(tz), sum(data_double), max(data_double) FROM large WHERE tz > '2018-09-23 14:47:30.298302-07';
┌──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
│ QUERY PLAN │
├──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┤
│ Finalize Aggregate (cost=1495625.42..1495625.43 rows=1 width=24) (actual time=5214.622..5214.622 rows=1 loops=1) │
│ -> Gather (cost=1495625.20..1495625.41 rows=2 width=24) (actual time=5214.468..5216.057 rows=3 loops=1) │
│ Workers Planned: 2 │
│ Workers Launched: 2 │
│ -> Partial Aggregate (cost=1494625.20..1494625.21 rows=1 width=24) (actual time=5179.552..5179.552 rows=1 loops=3) │
│ -> Parallel Seq Scan on large (cost=0.00..1417317.00 rows=10307760 width=16) (actual time=0.012..2671.871 rows=33333333 loops=3) │
│ Filter: (tz > '2018-09-23 14:47:30.298302-07'::timestamp with time zone) │
│ Planning Time: 0.111 ms │
│ Execution Time: 5216.123 ms │
└──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
(9 rows)
Now obviously those queries are somewhat nonsensical, but IMO they're not an unrealistic representation of what kind of operations happen in analytics queries. If you disable parallelism / add more aggregates / more filters, the benefit get bigger. If you add more joins etc, the benefits get smaller (because the overhead is in parts that don't benefit from JITing). postgres[24261][1]=# set jit = 1;
postgres[24261][1]=# EXPLAIN (ANALYZE, TIMING OFF) SELECT min(tz), sum(data_double), max(data_double) FROM large WHERE tz > '2018-09-23 14:47:30.298302-07';
┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
│ QUERY PLAN │
├─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┤
│ Finalize Aggregate (cost=1495625.42..1495625.43 rows=1 width=24) (actual rows=1 loops=1) │
│ -> Gather (cost=1495625.20..1495625.41 rows=2 width=24) (actual rows=3 loops=1) │
│ Workers Planned: 2 │
│ Workers Launched: 2 │
│ -> Partial Aggregate (cost=1494625.20..1494625.21 rows=1 width=24) (actual rows=1 loops=3) │
│ -> Parallel Seq Scan on large (cost=0.00..1417317.00 rows=10307760 width=16) (actual rows=33333333 loops=3) │
│ Filter: (tz > '2018-09-23 14:47:30.298302-07'::timestamp with time zone) │
│ Planning Time: 0.121 ms │
│ JIT: │
│ Functions: 8 │
│ Inlining: true │
│ Optimization: true │
│ Execution Time: 2440.328 ms │
└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
(13 rows)
Time: 2440.891 ms (00:02.441)
postgres[24261][1]=# set jit = 0;
postgres[24261][1]=# EXPLAIN (ANALYZE, TIMING OFF) SELECT min(tz), sum(data_double), max(data_double) FROM large WHERE tz > '2018-09-23 14:47:30.298302-07';
┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
│ QUERY PLAN │
├─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┤
│ Finalize Aggregate (cost=1495625.42..1495625.43 rows=1 width=24) (actual rows=1 loops=1) │
│ -> Gather (cost=1495625.20..1495625.41 rows=2 width=24) (actual rows=3 loops=1) │
│ Workers Planned: 2 │
│ Workers Launched: 2 │
│ -> Partial Aggregate (cost=1494625.20..1494625.21 rows=1 width=24) (actual rows=1 loops=3) │
│ -> Parallel Seq Scan on large (cost=0.00..1417317.00 rows=10307760 width=16) (actual rows=33333333 loops=3) │
│ Filter: (tz > '2018-09-23 14:47:30.298302-07'::timestamp with time zone) │
│ Planning Time: 0.041 ms │
│ Execution Time: 4140.116 ms │
└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
(9 rows)
Time: 4140.499 ms (00:04.140) - 25.69% postgres jitted-24373-18.so [.] evalexpr_3_3
- 96.00% evalexpr_3_3
ExecAgg
+ 2.89% __isnan
- 12.15% postgres jitted-24373-16.so [.] evalexpr_3_0
- 99.38% evalexpr_3_0
ExecSeqScan
ExecAgg
+ 0.56% ExecSeqScan
- 11.74% postgres postgres [.] heap_getnext
99.83% heap_getnext
ExecSeqScan
ExecAgg
+ 9.31% postgres postgres [.] ExecSeqScan
+ 7.66% postgres libc-2.27.so [.] __memmove_avx_unaligned_erms
+ 5.61% postgres postgres [.] heapgetpage
+ 5.36% postgres postgres [.] ExecAgg
+ 5.35% postgres postgres [.] MemoryContextReset
+ 4.11% postgres postgres [.] HeapTupleSatisfiesMVCC
+ 3.53% postgres postgres [.] ExecStoreTuple
+ 2.55% postgres postgres [.] hash_search_with_hash_value
+ 1.63% postgres libc-2.27.so [.] __isnan
+ 1.16% postgres postgres [.] CheckForSerializableConflictOut
so it's pretty clear that there's plenty additional work can be done to improve the generated code (it's pretty crappy right now), and that there's potential around improving things due to batching (mainly for increased cache efficiency).Informix (now dead?) vs. Oracle is another classic example from the past.
Oracles's domination absolutely does not imply its technical superiority, actually popularity is usually a bad thing (hello, Ayn Rand) - Java and junk food are insanely popular.
Well-researched thing will always beat fast-coded-to-market crap. Erlang (despite all its syntactic ugliness), Go (especially stdlib and runtime, which is hard), Scala, Redis, nginx, Haskell (monad-madness aside), Clojure etc, etc.
Ability to produce a lot of crappy code quickly is not a substitute for a decent academic research with proper attention to details. This is the point. Postgres was a research vehicle in the past, now polished to a state-of-the-art product, just like SQLite.
How is MongoDB, by the way? Still a default choice or it has been recently obsoleted by a blockchain?