PostgreSQL at low level: stay curious
erthalion.info
erthalion.info
>"But memory request is actually used only internally e.g. to calculate oom_adj and QoS."
Could someone elaborate on this? Doesn't Kubernetes also use this to decide if a container could be scheduled on this node in the first place? Or am I missing some other obvious point the author is trying to make?
In cases where you want to run functions on the server for performance (rather than for hiding some underlying structure), it's often more convenient to maintain them together with other client code.
You can do things like creating functions in pg_temp once per connection, but more upstream support for this sort of setup would indeed be pleasant.
And there's no PREPARE IF NOT EXISTS, so you either have to track yourself whether you've already done it or grub around in pg_prepared_statements.
Maybe if there is a case where things are absolutely faster to do in the DB, but often times, you get better performance and scaling when you ship that stuff off to the application (it makes things easier to maintain as well).
I'm currently dealing with a legacy system where some wing-nuts decided to push all the business logic into the DB. It is really REALLY hard to improve things like performance and scaling when that happens.
I'm curious why you couldn't write a stored procedure, then call that from your application?
If you need to see if your function exists, you can do something like this [0].
Also using pg_proc, you can get the DDL of your function e.g. [1]
SELECT prosrc FROM pg_proc WHERE proname = 'function_name';
And you could then check for equality of the source (or its hash), if you are tied to your current application logic[0] https://stackoverflow.com/questions/24773603/how-to-find-if-... [1] https://stackoverflow.com/questions/3524859/how-to-display-f...
It's all doable, but it's fiddly. Explicit support for this sort of setup would be a good thing.
DO can be good for this, but it doesn't cover all cases: sometimes you want a named function to use in an expression, and sometimes you want multiple procedures which can call each other.
Just having CREATE [OR REPLACE] TEMPORARY FUNCTION would be pleasant.
CREATE FUNCTION pg_temp.f(int, int) RETURNS int LANGUAGE sql AS $$ SELECT $1 + $2 $$;
SELECT $1 + $2 $$;SELECT pg_temp.f(1 , 3);
f
---
4
(1 row)(In practice, when I've wanted this sort of thing it's been good enough to either install all the functions the client might want to use whenever it makes a connection, or else to use CREATE OR REPLACE FUNCTION every time it's going to run a query needing that function. But I can imagine cases where neither of those would be efficient enough and I'd end up having the client track what it's already loaded. Some help from the server would be good there.)
Now how is versioning managed? The database can easily fill in here with a standardized approach, with supporting tooling.
Or it can not, in which case you come up with an adhoc versioning scheme that you maintain manually.
Same way you do in all computing scenarios when you don't have any versioning tool available to you:
AGREEMENT_v1.doc
AGREEMENT_v2.doc
AGREEMENT_v2_revised.doc
AGREMEENT_v2_Revision2.doc
AGREEMENT_v3.doc
AGREEMENT_v3_fixed.doc
or perhaps you start doing some strange behaviors to force other tools to fit in, like attempting to version your schema/functions with git, by tying it to a single application's ORM
or perhaps you stop touching the database directly, and try to make it all work with migration-scripts, and it all falls apart when a second application comes into play
or perhaps you try to force coordination between all dependent applications and your database, and make this update simultaneously
or perhaps you convince the RDB management system to manage your system
Additionally, in the case of DBs, it's strange to call it a "language"; at best it's a language with a supremely massive runtime, but more appropriately, it's a platform.
And being a platform, it would be fairly reasonable and unsurprising to consider things like version control and function-versioning to be in scope, even though most DB platforms don't handle it either.
But I'm also consistently surprised by how lacking the DB tooling space is compared to programming languages; eg the formatters are generally lacking/incomplete (foolishly imagining the SQL standard to be sufficient to claim support for all DBs), the general lack of any version control, the general lack of SQL transpilers (again, probably due to the misunderstanding that the standard standardizes anything; specifically the lack of a 80-90÷ OS automatic translation for MySQL -> PGSQL is astounding), the general lack of IDE support (Most can't even assist in maintaining datatypes when writing SQL, despite the all-knowing schema available to it; you basically get notepad + a pretty view of the output), etc.
DBAs are willing to put up with quite a bit, and I can't figure out why. The engines themselves are phenomenal works of engineering; the usage of it is utterly pathetic
Perhaps they don't want their programs to bloat until they can read email.
The functionality/tooling is missing, or at best, lacking, and has nothing to do with a preference for no-bloat. It simply means that we have arbitrary, adhoc implementations to enhance the management of our failing management system. That the database must be babied is not a positive, and RDBMS are meant to remedy that.
PostgreSQL gives you functions, views, rules for query writing.
Plus it gives you ephemeral versions of all those so you don't have manage it separately from the client code.
How so? There is no versioning support. That's it. I'm not sure how much more deficient you can get. Anti-versioning features?
>PostgreSQL gives you functions, views, rules for query writing.
Right, which requires an adhoc versioning mechanism, or coupling application + DB updates. Thankfully it was at least realized versioning your data made sense (MVCC), likely a result of how often problems occur trying to manually version it in your application logic with multiple arbitrary threads (read: humans, applications) operating on it, but somehow got lost on the rest of the system (where multiple arbitrary humans, applications fiddle with it).
>Plus it gives you ephemeral versions of all those so you don't have manage it separately from the client code.
Moving all of your database logic into your application code is very much a poor state of affairs, and defeats much of the purpose of offering functions, views, etc in the first place. Ideally, you want them to live for more than a single transaction, and be used by multiple applications, and presumably written & maintained by your DBA, who presumably knows the database engine, and its usage patterns, better than your application developer.
The ephemeral version is ideally a method of last resort (for one-off usecases); but if you want decent versioning tooling (eg git), this is your method of first resort.
Application developers learned this lesson decades ago. What I've never understood about databases is that despite the massive industry, usage, and ecosystem, their tooling is still stuck in the 80's.
> versioning your data made sense (MVCC)
In PostgreSQL, functions can be created and versioned with MVCC exactly like data.
> Moving all of your database logic into your application code is very much a poor state of affairs
Okay, then deploy it separarely.
> written & maintained by your DBA
But isn't that the "ceremony" you railed so hard against?
> tooling is still stuck in the 80's
Rather I think you want them to do something (though I'm not exactly sure what) that most other people don't. The tooling for Apache config files is stuck in the 90s too.
> Application developers learned this lesson decades ago.
What lesson?
Is it exactly as hard to install updates to your application, and is it to install updates to your database.
Is it exactly as hard to offer a new API version from your HTTP server (/v2/) as it is from your SQL server.
Perhaps not the best statement; I was trying to tie the notion of MVCC to function-versioning, except that function-versioning is at human-speeds, and has the same problems that lead to MVCC (but mitigated due to how slow the process is [not computer-speeds] and humans can actively intervene to manually correct)
>Okay, then deploy it separarely.
Deploying your changes isn't the problem. Tracking the changes is. The database exists as a globally available resource of statically defined functions accessible to all applications (and humans) interacting with the database. It pretends that there only ever exists one correct interpretation of the database, but in fact the database is ever-changing, and ever-racing. This model was learned not to be valid for data, and so we see "split timelines", that is, different views of the same dataset, with defined semantics on how to collapse the views. This is MVCC. But this was not replicated across the rest of the system. The data is versioned, internally, by the DB. Nothing else is. Yet everything changes.
>But isn't that the "ceremony" you railed so hard against?
No? What ceremony? Nothing exists!
My problem is that there is no defined process, and that its completely adhoc and manual, which means tooling can never properly exist -- not that it's difficult. It's error-prone; the ceremony doesn't matter. And importantly, we know a solid process can be defined (see: version control, or redis's model.)
>Is it exactly as hard to install updates to your application, and is it to install updates to your database.
Installation doesn't matter; Databases are great at updating their functions -- it just drops and replaces whatever exists! I'm talking about tracking function change over time. That the database exists in multiple states at different instances of being queried. This is automatically handled for data, but not logic.
>Is it exactly as hard to offer a new API version from your HTTP server (/v2/) as it is from your SQL server.
This is true, and I'm not happy with this either :-) You can't independently update your HTTP server and your client code for the same reason you can't independently update your HTTP server and your DB, without individually & manually tracking every function change. As a result, you're forced to couple releases of your application with releases of your HTTP server with releases of your DB changes, or track it manually (_V2).
I.e. a world where "FooClient v1.1.0" wants to interact with "DB schema Core-1/FooClient-v1.1.0", while allowing "FooClient v1.0.0" to still see "DB schema Core-1/FooClient-v1.0.0", and "BarClient v1.0.0" to see an entirely separate "DB schema Core-1/BarClient-v1.0.0"?
(I mean, sure, you could have each client construct its "API layer" of sprocs et al in its own client-specific schema, or worse yet, as its own connection-specific temporary objects—but that's pretty dang inefficient when tons of different clients are sharing the same few API versions, and when the different API versions share identical copies of 99% of their sprocs.)
Similarly, I'd never let a client modify the application logic running on my app servers.
I mean, I can imagine it, but it sounds like painting "root me and do what you will with the corpse" on your front door.
The alternative viewpoint, is one where a DBMS isn’t its own Data Warehouse, but is rather a “relational transformation workspace” of a Data Lake. Consider BigQuery as a DBMS. Who determines BigQuery’s schema? Nobody. Each user has their own stuff in their own namespace, and users mount stuff from one-another’s namespaces into their own namespace. The job of the people who run BigQuery (or any other multi-tenant data-warehouse infrastructure of this type) is just to enable users to collaborate with one-another through the DBMS, where Data Definition “APIs” are contracts established between pairs of clients, rather than between a developer and the DBMS’s DBA.
That was more the stance I was talking about with my parent post.
But even under a Database-supremacist viewpoint, what I was talking about can still happen. Whether clients are pushing objects into the DB or not, as long as there are requirements to not break old clients, but also requirements to evolve the schema for new clients, the DBA will arrive at a set of constraints that can only be solved by doing exactly what these clients would do themselves, if given DDL privileges: to define separate “virtual namespaces” where the same object names refer to mostly-common but some slightly-different versions of the same database objects, in order to allow LegacyClient to see PolyfilledLegacySchema while NewClients see the regular, “true” schema (or vice-versa.)
In any organization with a sufficiently-large team structure that must share data (picture e.g. Amazon with their store), there will be a number of teams (clients) with different conflicting constraints upon the same data set. This makes the job of the DBA managing their shared data, very similar to the job of an upstream library software developer: you must keep the library’s API stable so that existing downstream clients won’t break; while also making changes to those APIs such that new downstream clients can get the features they’re demanding.
Sure, there are alternate ways of solving this stable+evolving constraint problem for shared data, just like there are alternate ways of solving the stable+evolving constraint problem for a software library. You can just build a database that’s “downstream” of your own, that decorates the data with the new features (or tell the client that needs it to do it themselves!), and which syncs changes back to the upstream DB in the upstream’s canonical format. This is like building one library to wrap another. But this is, in the end, a lot of extra complexity that introduces the chance of data corruption along the way. Much safer to warehouse your data in one system with one MVCC world-clock. It might be a requirement if your upstream DBMS is “weak” (e.g. some legacy mainframe DB2 instance) but modern DBMSes give you every feature you need to provide multiple interfaces from a single instance. Why not take advantage?
———
And, if you can accept that argument—and you also accept the idea that clients should be able to use the DBMS as a scratch workspace for their queries (i.e. that creating temporary tables isn’t a perversion of what DBMSes stand for)—then combining the two results in the idea that each client could, in theory, just tell the DBMS of its particular needs for an API to access a shared dataset, and that this would result in the automatic creation of a virtual namespace perfectly suited to that client’s needs.
Sure, let’s pretend the client is specifying this virtual namespace using some sort of abstract specification language, where the DBMS takes it, parses it, and then has the power to satisfy those requirements however it wants, maybe just by directing the client to the nearest matching existing virtual namespace, etc. But think about what exactly clients would need to send in these requirements specifications for a long time, you’ll realize that the specs will still turn out to need exactly the level power of regular SQL DDL statements. You don’t need a shim layer; you just need a sufficiently-granular ACL system, such that the client can create “virtual stuff” for their own use, but can’t create or interfere with “real stuff”, or with other clients.
(And then it turns out that, since the client would just be communicating its needs via SQL DDL anyway, it makes perfect sense to let dev-teams avoid Not-Invented-Here by just reusing the perfectly good SQL-DDL-emitter that is “database migration frameworks.”)
PREPARED STATEMENTS: Queries that are executed often. Setup on every new session but will be automatically compiled, cached for the session lifetime, and executed with just a minimal identifier. Depends on your client driver to handle seamlessly.
More info: https://www.postgresql.org/docs/current/sql-prepare.html
Plus you have manually deal with this which is weird coming from MSSQL where it just does it with a global cache based on statement text hash all the time.
Brings me back to early 2000's when it was recommended to always use stored procedures because either MSSQL 7 or 2000 can't remember didn't cache ad-hoc statements only sprocs.
Usually the database client drivers will handle this for you. If you're using .NET, the npgsql driver maintains a connection pool and creates prepared statements for each new session. If you have something like PGBouncer in the middle then it gets more complicated.
That benefit is so very minor compared to the time taken to parse and plan the statement that almost none uses it, for more details:
https://dba.stackexchange.com/questions/129659/what-is-the-s...
In PG its seems to be the only choice and on top of that isn't even shared among connections, this honestly blows my mind and will have to remember if I move anything serious over to PG.
I like PG a lot and it does a lot of thing MSSQL doesn't but this is a big missing performance piece.
The only issue is dependency tracking in case you change any schemas but that's an issue with any saved code in the database.
Unless each binary gets its own hash (in which case you could solve it by building the SP when the package is built and then injecting it into the binary) there's some piece of information you'd use to chose which one to use - why obfuscate that information behind a hash instead of just using it directly as a descriptor?
I wanted to be able to have multiple APIs wrapping the database (one GraphQL layer one that would make sql queries directly and be consumed by an iOS and web app, and another traditional REST one designed for integration with third parties that don’t support GraphQL). I could have either duplicated that logic to sum up the values across those layers or made a shared api later underneath the REST and GraphQL layers (or put the rest layer underneath the graphql layer), but those other options seemed less ideal. I wanted the API layer only to be responsible for sending data to and from the database, authentication and permissions.
That app was a side project that I lost interest in, so I never got to see the long term implications of that, but I still think stored procedures were the right call for that particular problem. As much for data consistency and layer responsibility reasons as for performance reasons.
The main difficulty with stored procedures seems to be that they’re kind of hard to test and isolate. But when used sparingly, and when you test that the effects your expecting happen on writes (even if you can’t test the procedure explicitly), I think they’re a good way to keep purely data consistency related logic inside the database.
The cache will get evicted based on time with no use and memory pressure etc.
I am actually kind of surprised to find that Postgresql doesn't do this also, wow, I thought pretty much every modern RDBMS did this.
Is there some complexity I am missing that would require huge R&D , like its too hard to share the plan and procedures across sessions or the hashing algo would have too much overhead?
There are presumably also concerns that the same text can have different meanings in different sessions because of different per-session configuration, like the search path.
Nowadays, I think it's more that the gain isn't terribly large.
Parsing SQL is pretty cheap.
Normally with PostgreSQL you re-plan for each execution anyway. If you're going to ask it to reuse plans, the overhead of planning once per session is probably small enough that reducing it further doesn't gain much.
And with modern core counts and scalability concerns, there's a lot to be said for per-process rather than global caches anyway.
MSSQL also uses per session settings as part of the cache key. This can be confusing if you think its only based on statement text and wondering why two different client are getting different plans, its because they have different settings.
The planning overhead would completely destroy some applications I have worked on. Some more complex queries execute extremely fast but take some time to plan. Its hard for me to believe planner is so much faster as to be a non issue.
Global caches are key to db performance thats why PG has shared buffers. MSSQL is single process multithreaded though while PG is process per connection so I would imagine that complicates shared data structures somewhat.
SQL server does not recover quickly after a cached plan goes wrong. More than once, either adding "WITH RECOMPILE" or manually recompiling a SP has fixed our performance issues.
"Everyone has a plan until they get punched in the face" - Mike Tyson
With that kind a of plan instability I start looking at query hints though which PG doesn't have either...
Sure, it is possible to apply advanced techniques to try to guess when re-planning is worth it. Alternatively you can make it an option for PostgreSQL to cache parametrized queries too and accept the possible regressions, but that becomes less interesting due to most ORMs sending ["SELECT * FROM t WHERE x = 42"] rather than ["SELECT * FROM t WHERE x = $1", 42] which means you cannot simply hash the string.
Again it is possible to do but I think you underestimate the work necessary for it to be a widely useful feature. And on top of this there are probably legacy code reasons like PostgreSQL process based architecture which makes it even more work.
Also databases are designed for SQL, not ORMs. Not using parameterized queries at this point is just archaic and there are lot more problems then query caching to deal with first. The commercial databases can also tokenize input strings and parameterize them after the fact, this is what happens when you submit things as plain text and still use a cached plan.
That applies to queries but not more general TSQL with branching logic, etc.
> wow, I thought pretty much every modern RDBMS did this.
Both PostgreSQL and MySQL have plan caches using prepared statements. (SQL PREPARE statement)
PostgreSQL further allows tuning of whether the query plan is specialized based on parameter values.
That is not my understanding, all of the TSQL including procedural logic is "compiled" but it is not optimized because it is not a set based operation. However no parsing or binding needs to be done on it if reused.
The entire TSQL statement including branching logic is run through the parser and algebrizer and turned into a binary form that the optimizer can work on then cached.
>PostgreSQL further allows tuning of whether the query plan is specialized based on parameter values.
SQL server has prepared statement but they are basically unnecessary there is no need to manually tell it to prepare and reuse as it will do so on its own, it mostly there for backward compatibility.
SQL server will also optimize based on parameter values, lookup parameter sniffing, it can store multiple plans and may not use a stored plan based on parameters. It will also potentially auto parameterize sql statements that don't use parameters so they can be reused.
Again I have used sql server going back to 6.5 when it didn't cache ad-hoc queries and you had to use sprocs to get caching. During that time almost every app went through sprocs for every call because of the overhead of parsing and planning, its was like a 2x speedup.
Nowdays we have a lot more network memory and cpu, I still don't want my db server wasting it on parsing and planing the same statements over and over for no good reason. What programming language even interpreted doesn't keep some optimized ready to run version around for repeated calls and instead reparses and binds on every hit?
Yeah, I agree.
But I typically don't see it as a big problem because (1) queries are short so parsing is nothing (2) in some cases you'll want query plans to depend on parameter values, which hurts any cache rate (3) personally I heavily batch operations, e.g. a big insert is simply:
INSERT INTO example
SELECT * FROM unnest($?::int[], $?::text[])
P.S. AWS Aurora PostgreSQL has a plugin with global query plan caching and management. https://docs.aws.amazon.com/AmazonRDS/latest/AuroraUserGuide...Where you get big wins in plan caching are more complex selects that are run repeatedly from an application on multiple connections usually from a pool of say 100. These selects with many joins take significant time to parse and plan but execute very fast. This is pretty typical in a typical business app that is read heavy calling common selects over and over, like an entity find or read with security checks.