Features I'd Like in PostgreSQL
gilslotd.com
gilslotd.com
1) Simple, easy to use, high-availability features.
2) Horizontal scalability without having to resort to an extension like Citus.
3) Built-in connection pooler.
4) Query pinning.
5) Good auto-tuning capability.
And over time it will increasingly relegate PostgreSQL to being for development only with production use being handled by wire-compatible databases e.g. Aurora.
I think it's free for a single DB
Just curious, what would save you having the solution in-core? Installation, sure, but that's a one-off possibly in your deployment code. "CREATE EXTENSION citus" and add that to postgresql.conf? Sure, but not too much work for me. The rest (commands to actually create the nodes, do the sharding itself) are something I cannot imagine being different or simpler if with an in-core solution.
What am I missing?
And most importantly it means you don't have to worry if that extension will move to a freemium model which (a) often has important features out of your price range and (b) is generally unacceptable in enterprise environments.
Yet in the particular case of Citus, history (so far) has shown a) that they update the extension regularly and fast, so by the time you want to upgrade to a newer major version you already have Citus updated too; b) they are going exactly in the opposite direction of "freemium", they actually open sourced even the previous proprietary bits; c) as OSS, it can always be forked and if one day closed source, being such an important project, it would be definitely forked.
(I don't have any stakes on Citus)
Scaling databases is an expert skill that is far out of reach for almost everyone.
Far easier just to pick another database that includes HA and horizontal scalability out of the box.
If Citus would become proprietary overnight, my main concerns of maintaining a fork would be around the codebase and the language expertise more than the sharding concepts.
Note that sharding is different from a purely distributed database. The latter is an entirely different class (and more complex system).
Citrus also has one glaring problem: single point of failure due to a single monitor node.
Postgres would blow every other database away if it had HA, multi-write nodes and fault tolerant feature similar to FoundationDB or TiDB.
While Postgres is OLTP. For OLTP databases achieving horizontal scalability require a more sophisticated approach to distributed consensus, like in CockroachDB or Spanner.
Currently, supporting a feature like "users can reorder the items in a playlist" is typically done using an integer position column. However, this doesn't prevent gaps (so e.g. the third item in the playlist might have `position = 42`), and inserting between two other items requires updating every row with a greater position value.
I'd like to be able to say `update ... set position = 3` to make the record the third item in the list. You'd need to be able to set the scope (eg `add column position ORDINAL WINDOW BY playlist_id` or something).
A DB type could be nice to hide away the adjacency list.
The other suggestion here to use fractional numbers is similar to materialized path (ltree data type in Postgres) and has been formalized to apply to hierarchies with Farey Fractions: https://sigmodrecord.org/publications/sigmodRecord/0506/p47-...
update ... set position = (prev + next)/2;
To get better performance you can choose to use the float data type, but then you'd be limited to a fixed precision; sufficient for most cases, though.Also, you’ll still eventually need to go clean up the entire sequence because you’ll run out of gaps between adjacent numbers. Because of this, I’d probably rather use a more predictable type (like one of the integer types) and explicitly plan my cleanup schedule.
If you're using a numeric type, you get up to 16383 digits after the decimal. That's... Probably more precision than you'll be able to reasonably use up in almost any use case. Any time you're reordering in bulk, you're resetting the order value to a nice integer, so it would take many thousands of ad-hoc reordering operations near a single position to get it close to the precision limit, yeah?
https://www.postgresql.org/docs/current/datatype-numeric.htm...
------------
playlists {
id
}
------------
playlist_members {
id
playlist_id
prev_playlist_member_id
(and/or next_playlist_member_id)
song_id
}
------------
you could then just select * from playlist_members where playlist_id = ... and sort on the client side. you'd probably add an application limit where playlists have a max length of some kind.
re-orders can be done in a fixed number of row updates and typical application queries are still possible / fast.
playlists {
id
song_ids []
}-------------
and just store the ordering in an array. Some postgres drivers might start shitting the bed though at some gigantic array sizes, but a playlist probably has reasonable enough limits that you wouldn't have a big problem.
You could probably also sort in the database with a recursive cte or with PL/pgSQL.
I’ve never considered doing this though! I imagine I must’ve come across some problem where this would’ve been better than whatever I came up with.
Example constraint would be a unique index for the Playlist member table on the combination of Playlist id and previous Playlist member ID.
I know the postgres devs don't like them, and that the query planner should be good enough that they're not needed, but it's not, and it regularly fucks up.
The closest analogy I know of to that, is how you work with ETS tables in Erlang. I want to send the RDBMS code that operates at that level!
Actually, I presume that SQLite would necessarily have some low-level C interface that works like this — but few people seem to talk about it/be aware of it compared to its high-level SQL-level interface.
There were some attempts back in the early 2000s to move Exchange over to use the SQL Server RDBMS engine, and they added a bunch of features to enable this kind of low-level control. Not just join hints: you could force specific query plans by specifying the plan XML document for that query. See: https://learn.microsoft.com/en-us/sql/t-sql/queries/hints-tr...
This wasn't good enough however, and Exchange still uses the Jet database.
Something that might be interesting is an RDBMS "as a library", where instead of poking it with ASCII text queries, you get a full programming API surface where you can do exactly the type of thing you propose: perform arbitrary walks through data structures, develop custom indexes, or whatever.
IIRC JVM static-analysis libraries get around this by essentially forcefully pulling in and reflecting upon the particular compiler release's internals that are being built against. The result is "non-portable", but only in the sense that it's getting tailored to the particular compiler release that's already concretely available in the build environment.
Mind you, that's a bit different, because you don't usually ship the compiler parts of the JDK as part of your application JAR; while SQLite does ship this compiler as part of the library. Would be fine, though, as long as your executable's embedding SQLite statically (or in a Docker image, etc) — in other words, vendoring the particular version of SQLite that matches the version the application-layer codegen library was compiled against.
Nothing like the planner deciding it knows better at some random time in production because of data changes.
Why should a camera's software written 5 years ago in Japan/China/Taiwan choose for me with the lighting conditions I have right now in Seoul at 2:30 in the morning?
That's why most professional prefer to use a manual mode. Auto is often used as a first suggestion (but not a very good first suggestion).
The same is of course true when you have big joins that don't fit in work_mem but default size of this will be much larger.
I can usually fix bad plans with CTEs, no need to get much fancier. And the problem is often caused by schema design where you have a mapping table of two tables in the middle and your join is N-M-M where the planner has no information about the relationship between the two outer tables.
my setup is local nvme ssd raid, so I hope this part won't be bottle neck. Also, if you are doing heavy join, where join order and method is need to be controlled, you temp table likely will be large, so you will need to be ready to have disk io.
Within PL/pgSQL, CREATE TEMPORARY TABLE is still (sometimes a lot!) slower than just SELECTing an array_agg(...) INTO a variable, and then `SELECT ... FROM unnest(that_variable) AS t` to scan over it. (And CTEs with MATERIALIZED are really just CREATE TEMPORARY TABLE in disguise, so that's no help.)
But isn't this exactly how temp tables work? A temp tabe lives in memory and only spills to disk if it exceeds temp_buffers[1].
[1] https://www.postgresql.org/docs/current/runtime-config-resou...
Just spitballing here — I think the difference might come from where the metadata required to treat the table "as a table" in queries has to be entered into, and the overhead (esp. in terms of locking) required to do so.
Or, perhaps, it might come from the serialization overhead of converting "view" row-tuples (whose contents might be merged together from several actual material tables / function results) into flattened fully-materialized row-tuples... which, presumably, emitting data into an array-typed PL/pgSQL variable might get to skip, since the handles to the constituent data can be held inside the array and thunked later on when needed.
https://learn.microsoft.com/en-us/sql/t-sql/queries/hints-tr...
There could also be hints that tell what kind of join to use and which indexes to use!
https://docs.aws.amazon.com/AmazonRDS/latest/AuroraUserGuide...
Ran into this one recently, and worked around it by executing a CLUSTER command after inserting data. By CLUSTERing on a column that contains random data (which your test could inject) you force Postgres to randomise the row order on disk, and thus randomise the return order.
In my case our primary key is a random UUID (I know, it’s a terrible thing, it’s not my choice), which is perfect for the task as CLUSTER requires an index. You might be able to pull the same trick by creating an index in a transaction, CLUSTERing then rolling back the transaction, but I suspect that you can’t call CLUSTER in a transaction.
Not as nice as proper test mode, but gets the job done, and avoids the need to wrap queries deep inside your application, with all the brittleness and peril that comes from using wizard level code reflection that’s normally required for such tricks.
On that same project we ran afoul of Javascript's 10^53 floating point limit with IDs, but I think that was a separate issue.
To solve the multi-tenant issue, I personally prefer either flakeID/hashID that have an ordered component and a random component to make walking IDs hard; Aggressively name spacing your data so a customer ID plus an object ID is always needed as pair to look something up, so trying to walk object IDs can only every result in someone accidentally looking up objects that already belong to them; edge layers that remap and filter internal identifiers via hashing etc so externally all ID are opaque and random; strong and careful access control, that ensures you can only ever lookup ID that belong to you, with careful consideration for side channel timing attacks.
Relying on random UUIDs for customer privacy would raise red flags for me. If being able to walk ID is enough to break your security model, then I kinda wonder if you actually have a security model.
Knowing how much activity a competitor is adding into a project management system isn't a lot of data, but it's more than zero.
You should always be determine right to access a record before attempting to look it up. If you can’t determine ACLs without the lookup, then your permissions layer should always assume the requester doesn’t have permission to view a non-existent record and return a 403.
Doing anything else is dangerous, and randomising PKs is a sticky plaster over a badly designed access control system.
UUID version 7 features a time-ordered value field derived from the widely implemented and well known Unix Epoch timestamp source, the number of milliseconds seconds since midnight 1 Jan 1970 UTC, leap seconds excluded. As well as improved entropy characteristics over versions 1 or 6.
If your use case requires greater granularity than UUID vesion 7 can provide, you might consider UUID version 8. UUID version 8 doesn't provide as good entropy characteristics as UUID version 7, but it utilizes timestamp with nanosecond level of precision.
https://blog.devgenius.io/analyzing-new-unique-identifier-fo...
In other words, one CAN implement a UUIDv8 with nanosecond precision, but that is not a requirement of UUIDv8. 4 is random. 8 is a custom grab bag.
At least one distribute datastore (Google Cloud Datastore) works better with random keys rather than incremental keys, it tends to produce fewer tablet splits. At least, it used to, the technology may have changed since then.
Honestly it seems like the arguments for/against UUIDs as keys are pretty mild on both sides. Why would it be "terrible" to go one way or the other?
All in all not enough good reasons to make a lot of stuff slightly worse.
You can generate the first few bytes (we use the first 3) of the UUID from a timestamp - that gives you index locality. It's even better than an auto-inc because you can control exactly how much of your index will be used for hot inserts based on how many timestamp bits you use, so you can avoid lock contention around the single latest index page.
> Encourages uuids generated client-side.
It's really an API design choice about whether you allow this, but it can be useful in some circumstances (if you trust the client).
That is usually the total opposite of what you want. There are some optimizations for inserting to the last page but primarily it is because you want to to sequential inserts. So if you want to avoid contention on the last page you should insert ordered by (connection ID, sequential ID or high resolution timestamp). That way every connection will do sequential inserts on its own page.
- UUID (all types) scale, sequential ids don't scale
- UUID (fully random) is completely secure and private. You cannot infer anything from it. As soon as you add anything to it ( like timestamp or counter), outsiders can infer things like how fast you are generating new objects
- UUID (fully random) cannot be abused by developers, which might skip creating timestamp columns and read it from the id instead
- UUIDs are slower, but as you see y's not for no benefits
You keep 106 bits of entropy (from v4's 122 bits) while largely eliminating page faults. Walkability is eliminated while not leaking too much temporal info.
https://www.2ndquadrant.com/en/blog/sequential-uuid-generato...
This might just be a mistake, but a uuid is 128 bits = 16 bytes not 128 bytes. Of course yes there are still circumstances where the extra space (16 bytes vs 8 or 4 or something else) isn't worth it.
Is there an actual problem with this? I don't mean "it feels impure" - what's the failure case here that's severe enough to discard UUIDs as PKs?
The other reasons all boil down to 'database performance tuning', which is reasonable if you have any prospect of needing it, but most datasets are _tiny_.
There’s a lot more edge cases when you use client-side generated IDs. You have to check if the ID is actually new to avoid security issues is the main one. Let’s just upsert into the DB and return the results and the uuid is new and randomly generated so it’s fine! So simple! Except now Alice can send someone else’s uuid and read their data. X100 endpoints all need to handle this correctly. It’s a huge risk.
If you do offline first ID generation using global uuids, the possibility of conflicts (due to bugs, patched client code, etc) that’s a whole rabbit hole of edge cases and problems as well.
It can be done, Bret Taylor used that architecture for quip, but it’s needlessly tricky which is the whole story for UUIDs imo - annoying and slightly worse for many normal apps. If you’re building a complex distributed system and want to deal with all the trickiness, go ahead. I’d recommend you use a B64 custom identifier instead of UUID so that it’s copy paste able :-)
>Is there an actual problem with this?
The nature of your application and its security and idempotence needs will determine who should (ideally) generate ids for any specific context. The format is irrelevant.
Generate ids on the client when it makes sense, generate them on the server when it makes sense. Server-side is more common but if you choose poorly you'll make bad software either way.
Postgres can auto generate UUIDs too and can be used for pk
True, but the practice makes it easier to shift to the dark side and insert the child records first, as you can already know the pk of the parent record in advance.
I know you are trolling, and it is ok if done sensibly, but it is not simply purism that makes it a bad idea. Because you have to trust the client it opens the way to discovery attacks (try inserting a record with a specific PK -> it will fail if the record exists -> now you know that it exists even if you don't have access to that record).
You may also not have access to all client implementations (think of public APIs) so some client libraries might not implement proper (i.e. strongly random) UUID generation.
UUID are perfect fine identifiers in an entirely pure sense. But baking a like more info into those identifiers can make debugging and building operational tools so much easier. It’s just a massive waste to fill those 128bits with either random data, or very limited options that other UUID versions give you.
Arguments around leaking information into public I think are a little silly as well. There are plenty of ways of preventing that, trading ease of debugging for making your internal identifiers safe in public is a poor trade off in my view.
https://learn.microsoft.com/en-us/sql/relational-databases/t...
It makes so many things far easier, having a temporal relation for every record and saves a lot of headaches that would otherwise usually be dealt with on the application level.
In an environment built on "traditional" SQL you'd have a history table, where you first and foremost would have to maintain records for those values: - ticket ID - time of state change - type of state change
But for the stats to be actually valuable, you might need additional history context (different per type), such as the user ID, the state, criticality, etc for which either an additional table per history type item type would be required or the data could be stored as serialized value with a metadata column of the history table. One requires complexity on the DB level, the other requires application level logic and slows down report generation tremendously.
Temporal tables to the rescue to remove all this complexity.
The full state of a ticket record including it's related entities at the given time of an UPDATE is fully preserved by the DB natively, so all you'd have to do to retrieve historic records at a given time is to append "AS OF $TIMESTAMP" to your query.
In the end, there's no need for extra history tables, much better performance when building reports, no need for application level data wrangling, ...
What we need is native support for bi temporal data like MariaDB.
Example:
SELECT users.id, COUNT(*)
FROM users
JOIN orders ON AUTO
WHERE orders.created_at > NOW() - '7 day'::interval
GROUP BY ALL
This would only work if there was an obvious path to do the join. In this case, I'm imagining that the `orders` table might have a `user_id` column which is a foreign key into the `users` table.I think you are suggesting some sort of lookup based on the defined FK relation, but that would be confused by situations where tables have multiple FK relations, such as tables with values restricted by a lookup value table (or more than 1 such FK). Those are pretty common, so I could see the 'AUTO' feature breaking down quickly. I think that is why the NATURAL JOIN approach is taken and that basically does what I believe you are describing, provided the column naming is matched.
[0] https://www.postgresql.org/docs/15/queries-table-expressions...
[edit:spelling]
Example pulled from Google: https://sebhastian.com/natural-join-mysql/
https://www.postgresql.org/docs/7.2/queries-table-expression...
SELECT * EXCLUDE
SELECT * REPLACE
Column Aliases in WHERE / GROUP BY / HAVING
Struct Dot Notation
Function Aliases from Other Databases
are implemented in ClickHouse long before.> "Loose indexscan" in MySQL, "index skip scan" in Oracle, SQLite, Cockroach (very recently added), and "jump scan" in DB2 are the names used for an operation that finds the the distinct values of the leading columns of a btree index efficiently; rather than scanning all equal values of a key, as soon as a new value is found, restart the search by looking for a larger value. This is much faster when the index has many equal keys.
I ran into a case in a hobby project where a really basic GROUP BY query was performing was worse than it should. I was surprised that https://malisper.me/the-missing-postgres-scan-the-loose-inde... it's been known "missing" since 2017.
I think in ML-family languages it's fairly common to format like this, which avoids bigger diffs:
foo = [ 1
, 2
]
In C-style languages something similar goes as well, though perhaps used less commonly: int x[] = { 1
, 2
};It works very easily and consistently to center around the space and lead with the comma:
select id
, name
, address
, ts
from table
where condition
and etc; SELECT a.foo
, b.bar
, array_agg(z.gumball) gumballs
FROM zoo z
INNER JOIN alpha a
ON (z.z_id = a.z_id)
LEFT JOIN baker b
ON (a.a_id = b.a_id)
WHERE z.last_modified > '2023-01-01'
AND a.is_active
GROUP BY 1, 2
ORDER BY 2, 1
LIMIT 100
;
Left side highlights the operations. Commas and "AND" delimit parts of each directive. The semicolon lines up on the left side to help visualize the end of each statement in a long chain of commands, especially DDL.I also tend to capitalize the SQL keywords and leave the identifiers lower case.
SELECT foo
FROM bar
Which allows room for everything I commonly use, including INNER JOIN.Granted, adding at the end is more common, but you can't fix the problem with tricks (short of putting every comma on a line by itself too).
https://hakibenita.com/sql-tricks-application-dba#make-index...
There was an extension from Teodor some time ago, plantuner, to disable indexes in a session or globally ("set plantuner.forbid_index='id_idx2';") – but it didn't make it to core, and even to contribs. Maybe because the functionality to disable indexes was mixed with planner hints there. It's a very old story, discussion from 2009: https://www.postgresql.org/message-id/flat/47E63672-972E-452...
- disable for all - disable only for my session, to check what would happen with the plan, and only then decide to proceed with disabling for all (or to drop it)
ALTER is quite invasive way, even more than "UPDATE .. SET indisvalid = false ...". It would be good to do it via SET as it was proposed in the plantuner extension long ago.
I’d love to see B-Tree primary storage option. Aka store the row data inside the primary index. This can save a lot of space for thin tables or tables with large keys, and would be basically an auto-CLUSTER with all the performance that comes with that. This is how MySQL works. The hash table primary storage is better sometimes but it sucks for range queries leading people to need timescale for good data locality
Nope, it does not write anything to disk unless you have more data than work_mem.
It is coming: https://github.com/orioledb/orioledb
Postgres has this - they’re called “covering indexes”
https://www.postgresql.org/docs/current/indexes-index-only-s...
Note that there is a limit to index tuple size, which is lower than the limit on row size
With a covering index in PG you still have a heap storing the rows and a copy of the covered columns in the index.
With clustered indexes or IOT's the table is the index there is no duplication unless you have other secondary indexes. This save a lot of space and reduces indirection when seeking on the clustered index.
Some DB's like Sqlite and InnoDB (MySQL) this is always the case the table is a b-tree and there is always a clustered index even if you don't define one explicitly. In others like Oracle or MSSQL you have a choice of unordered heap or b-tree, in PG you have no choice the table is always an unordered heap and all indexes are secondary.
With this understanding I can't grasp the benefit of a clustered index -- if you're using the primary index (let's say numeric auto-incrementing ID) then you'd likely have a secondary index for that already (in PG). If you were searching by something else, the default clustered layout is a hindrance as you must traverse unnecessarily to find entries (rather than sequentially scan)...
The only penalty of PG's decision seems to be excess memory usage (storing a second copy of the identifying tuple contents in memory) -- is that characterization correct? But I wonder how this holds up with a spinning disk -- I wouldn't want to follow indices (and do random reads) on spinning disks.
Looking at the other side, I guess the case where PG shines is where you want to do batch processing (so looking at a page of tuples is good locality-wise, but you also often retrieve by the identifier, and don't mind paying the cost of [rows x identifier size] for the privilege.
Are there some specific use cases where clustering indices clearly outperform/are the right choice?
Clustered indexes can save significant space on a narrow table with a lot of rows accessed in a particular way and perform better as well, if you access the table in multiple ways with other secondary indexes it can be slightly slower to access through a secondary.
This can be improved in two ways. One, if you add a second index which gives you better locality in the index. For example (customer_id, product_id) will group up all the rows by customer id. This can reduce the # of pages for index traversals down to <5 as long as each customer doesn't have a lot of rows. And in many cases this makes the primary index on just id useless. This brings the total down to 105 pages give or take. (depends on how many products each customer has)
The other way is to use an actual primary index, or use a covering index so that the data you retrieve is already in the index. For example if you're just pulling product_name from your table, you can use covering index on (customer_id, id, product_name) so that the product_name has locality with the customer's product IDs. This would bring down the total pages to be retrieved down to maybe ~20, since product_name tends to be larger data. It's a question of how many (customer_id, product_id, product_name) tuples can fit on one 8KB page and how many products the customer has.
If you use a primary index, the whole rows are on pages. This lets you run queries that pull lots of data (or different data) and have good data locality, but it means less tuples per row so you need more rows. So you'd access maybe ~50 rows but this index could cover a lot of queries unlike the covering index which only works for product_name.
These days SSDs are much faster than hard drives, so # of rows pulled off disk is still important but not as much so. Another thing this buys you is that you don't pollute the in memory cache by evicting pages just to load a new page that doesn't get utilized well. For instance original the index leaf nodes that are just 4 bytes (product_id, rowid) so every one you throw away 99% of the data on that page.
(I sort of agree with the parent, in that, if you need this in a raw SQL interface, it's a pain. But it shouldn't be a common pain point?)
In pgAdmin I would have to expand every schema to see every table.
Was a bit tired when writing that comment.
(I'm a CLI user, so it's a \dt away. But I Googled screenshots of pgAdmin prior to commenting to see if it was in the UI, and it/was.)
It also reminds me how many DBMSs did not support the LIMIT clause because it is non-standard. But it is good from a usability perspective.
https://stackoverflow.com/questions/1528604/how-universal-is...
That's why ClickHouse has support for most of the extensions from the MySQL dialect.
Admittedly the internal tables and views have a bit of a learning curve and are a little bit obscure (and inconsistent) at times, but it's much nicer and much more flexible.
The CLI stuff like \d are just "aliases" for queries; \set echo_hidden shows them.
From the CLI \d etc. seems just as easy (easier actually), and you can add your own shortcuts if you want.
From outside the CLI, this sort of introspection is rare enough I don't really see the point in making special shortcuts for it.
You could probably implement the foreign key using generated columns these days. Ah I guess you mean instead of having the association table? That would definitely be nice in some cases.
Yes!
We very occasionally use integer[] columns with ids, but hold off on using them widely b/c of the lack of integrity constraints. It'd be awesome to use more often.
"it was 2:01am on November 5th, in America/Los_Angeles and the offset was -7"
"it was 2:01am on November 5th, in America/Los_Angeles and the offset was -7 (retrieved from ICU v97)"
E.G. https://www.rfc-editor.org/rfc/rfc5545#section-3.8.5.3 (Internet Calendar) still has the issue of events not tied to the time a 'normal' human might expect if rules change.
Lunch service 11am to 3pm Weekdays, 11 to 2pm Weekends
How would that dynamically map to changes in the timezones that might be mandated by the law but reflect the correct event times in a scheduling system? Spreadsheet systems developed the $ prefix for cell addresses as a shorthand to lock that in when adjusting the relative offsets in copy and paste / duplicate operations.
2. What is the collation (sorting algorithm) for this datatype?
I like Jetbrains DataGrip for this.
If you execute a modifying query without a where clause it will stop you and double check that's what you intended to do.
Likewise you can specify a database connection as read-only so that it wont run modifier queries at all, attempting to do so will stop you but you can then explicitly run it if you need to.
We emulate that in prod by having admin users with read only permissions. They are granted other roles without the INHERIT option, thus needing an explicit `SET ROLE ...` before being able to do anything dangerous.
I also teach people the habit of combining `BEGIN; SET ROLE ...` everytime they need to write something. It has completely stopped "woops prod" incidents since it was implemented.
Does it parse the SQL and guess if it’s DML or run it in a transaction and rollback after?
Because “SELECT foo(‘bar’)” could perform DML.
I created a function to modify then set the connection to readonly.
I have no idea what this entails or if it's even possible, I only know it would make my life easier.
Why? Being able to run a physical replica that loads WAL from multiple primaries that each have independent data you want to work with, for one thing. (Yes, logical replication "solves" this problem — but then you can't offload index building to the primary, which is often half the point of a physical replication setup.)
(no actual query layer obviously, these are raw heap-file scans)
I'm now studying, trying to implement a WAL and transactions + recovery.
Could you show some pseudocode for what you mean, because this sounds interesting and I _almost_ get what you mean, but not entirely.
In such a system, "single-world MVCC" would be what you'd get by putting everything into one database file, with any changes intended always done within one DB tx of that single DB file.
"Multi-world MVCC", then, would be what you'd get by opening multiple database files, each of which maintains its own WAL/journal/etc., and then creating an application-layer abstraction that allows you to coordinate opening DB txs against multiple open DB files at once, holding the result as a single handle where if you hit a rollback on any constituent tx, then the application-layer logic guarantees that the other DB files' respective txs will be told to roll back as well; and that when you tell this coordinated-tx to commit, then it'll synchronously commit all the constituent DB files' txs before returning.
Note that unlike with a single DB file managed through a single readers-writer lock, this kind of system can introduce deadlocks (but DBs with more complex locking systems, like Postgres, already have that possibility.)
Something like:
interface IDatabase {
wal: IWAL,
transactionManager: ITransactionManager,
// etc
}
And then your application has multiple instances of these "IDatabase" objects, maybe one per physical/logical database file, e.g. ".sqlite3" in the case of SQLiteAnd at the root of your application, you have something like an:
interface IDatabaseManager {
databases: Set<IDatabase>
transactions: Map<IDatabase, Set<Transaction>>
}Yes query plan reuse like every other db, this still blows me away PG replans every time unless you explicitly prepare and that's still per connection.
Better full-text scoring is one for me that's missing in that list, TF/IDF or BM25 please see: https://github.com/postgrespro/rum
You wouldn't need to store the prefixes at all, it'd be part of the column metadata, but it would be prefixed before sending the data back to the client.
I could do this at the ORM level but in my experience even with the greatest ORM in the world you often have to drop into raw SQL. Additionally people might have multiple ways of accessing the same db or a read-only duplicate.
Postgres allows one to write custom data types in C or other system programming language. The only limitation is that the "type modifier", in this case the prefix, would need to fit in a "single non-negative integer", so in priciple using e.g. 5 bits per character you could get up to 6 lowercase letters, `-`, `_` etc into the prefix. Of course a custom extension would have limited utility if it doesn't land into cloud services like AWS RDS.
The safeupdate postgres extension[1] does this exactly.
safeupdate is a simple extension to PostgreSQL that raises an error if UPDATE and DELETE are executed without specifying conditions
For example, if you are trying to drop a table of large enough size, ClickHouse will ask to defuse the protection first: https://clickhouse.com/docs/en/operations/server-configurati...
Better WAL replay speed. Its always surprising that a server can create WAL files much quicker than a replica server can replay them.
Online upgrades
Rather than this, why not do more with existing table REFERENCES metadata? For example, why can't I have a covering index across data from two tables, joined through a foreign-key column with a pre-established REFERENCES foreign-key constraint against the other table, where the REFERENCES constraint then keeps everything in place to make that work (i.e. ensuring that vacuuming/clustering either table also vacuums/clusters the other together in a transaction in order to rewrite the LSNs in the combined index correctly, etc.)
With a static DDL constraint on join shape, an assertion about such shape can be pre-validated at insert time, to be always-valid at query time, such that queries can then pass/fail such assertions during their "compilation" (query planning) step.
Without such a static constraint, you have to instead insert a merge node in the query plan (same as what a DISTINCT clause does) in order to normalize the input row-tuple-stream and fail on the first non-normalized row-tuple seen.
You could pre-guarantee success in limited cases (e.g. it's going to be a 1:N join if the LHS of the join has a UNIQUE constraint on the single column being joined against); but you're not going to have those guarantees in most cases of "computed relations."
The underlying issue, mixing up the cardinality of one side of the join, isn't a super tricky one to identify, especially once you've seen it once or twice or seen what the buggy output looks like. Every intermediate SQL developer has run into the bug and has fixed it. The vision I have for the feature is more like a set of training wheels for developers who are earning their SQL scars. And it's also probably not a terrible idea to document exactly where you are expecting the full M x N cartesian join results when you do want them.
I have some big materialized views that take a long time to refresh concurrently, and I wish I could refresh a subset of the entire view’s data.
REFRESH MATERIALIZED VIEW mymatview;
Which does a full view update.See https://www.postgresql.org/docs/current/rules-materializedvi...
FWIW Odyssey supports prepared statements in transaction pooling.
I wish there was a way to have "named" constraints that you voukd share between tables. Because they would behave akin to mixins and that would open up a world of possibilities.
(And if you're into that sort of thing, there's a lot of type theory concepts that could be applied on top of it that would make things much more practical and approachable)
Also, I'm eagerly waiting for Incremental View Maintenance to be merged into main postgres.
They are not perfect, but probably something close to what you are looking for.
AFFIRM TABLE product_category
(
category_id smallserial
PRIMARY KEY
, name text
NOT NULL
) COMMENT 'Product categories from the official catalog'
;
AFFIRM TABLE product
(
product_id uuid
PRIMARY KEY
DEFAULT gen_random_uuid()
WAS (id uuid DEFAULT gen_random_uuid())
, (id int8 DEFAULT gen_random_uuid() USING gen_random_uuid())
COMMENT 'Product lookup id'
, category_id REFERENCES product_category
ON UPDATE CASCADE
ON DELETE RESTRICT
NOT NULL
COMMENT 'Product category'
, serial_num text
NOT NULL
COMMENT 'Product serial number'
) COMMENT 'Our nifty products'
WAS (products)
;
Just like a SELECT statement describes the structure of the output with the engine figuring out how to retrieve it, instead of CREATE, CREATE IF NOT EXISTS, and ALTER statements over and over, just describe what your target is supposed to look like and let the engine reconcile. The WAS keyword (for example) could allow engines to figure out how to get from one state to the next. In this case converting the id column from an int8 to a UUIDv4 and then changing its name from id to product_id. The table was named products and is now product. No more Flyway or the like where you have to hunt through migration files to find the latest definition for a view or function.So much easier to diff when making changes. Then after all databases have been migrated, prune the old, obsolete WAS definitions. Also, why do we have to specify the type of category_id in the product table? It's a foreign key reference; the type is not only implied, it's mandatory to match exactly.
But since we're already here on the topic:
• Real pivot support, not that crosstab hack
• System-versioned temporal tables
• Append-only (log) tables
• Unstored computed columns
• Alter a table used in a view, so the view loses column(s) or gains
• SELECT * EXCEPT (<column names>)
• GROUP AUTO where the query uses any non-aggregate column(s) as the GROUP BY column(s)
• Automatically updated materialized views
• Access Postgres via WebSocketI've used PostgreSQL on a live system but switching back to MySQL on next project because I don't miss anything from PostgreSQL but I do miss this basic feature.
I've read somewhere this feature may be coming but I'd just switch back today.
I work with both MySQL and PgSQL, and not being able to reorder columns easily is one feature I really miss in PG.
Edit: Current on-disk size is 5.34TiB and logical size is 20.3TiB so closer to 3.8x savings really
But if you want higher compression, you need to consider column-oriented DBMS, such as ClickHouse[1]. They are unbeatable in terms of data compression.
[1] https://github.com/ClickHouse/ClickHouse
Disclaimer: I'm a developer of ClickHouse.
I had to write some ugly stored procedures to attempt sanitising myself
2) Query batching where queries are submitted to a database together and then results are returned. If this could be done without a connection per query that would be awesome. The use case is a global truth database needing 1 write to 20 reads… If the server is in the UK and the client is in Fiji then batching the 20 reads together is a massive latency win.
https://innerjoin.bit.io/the-distance-dilemma-measuring-and-... https://www.postgresql.org/docs/current/libpq-pipeline-mode....
PostgreSQL also supports query batching via pipelining.
- should soft-deleted rows move to another partition to avoid bloat?
- what’s the column name and type (boolean, timestamp, tstzrange)?
- what’s the syntax for a soft delete versus hard delete?
- what’s the behavior of updating a soft deleted row?
- do soft deleted keys prevent inserts with the same key?
Bitemporal tables mentioned in the thread are a more general solution. Alternately, you could use a statement-based trigger to prevent deletes to the table.
Having said that I do like the simplicity of being able to configure the entire database to require where clauses on certain statements, it’s like a poor man’s multitenancy, without the complexity of RBAC.
Are there any downsides to that outside of more typing?
Ah I see the `–i-am-a-dummy` also affects queries with large result set sizes.
I’m definitely not a PG dummy but that’s what I do anyway.
It seems that some sort of graph querying will be part of an upcoming sql:2023 standard but detail is sparse. This repo seems to have collected some relevant links [0]
It's not really "coincidental". It's insertion ordering on-disk. And that varies between servers. So if you have 10 replicas they might persist records to disk in a slightly different order. So, if you don't specify an explicit ordering in the query then you will get records back in the order the server finds them on-disk.
Additionally it’s not even insertion ordering, more like update ordering. All row updates in Postgres result in a new tuple being written, which may be near the original tuple if there’s space, or may be at the top of the table on-disk. But either way, updates will also change your row ordering.
With replicas you would expect them all to have very similar, possibly identical on-disk ordering, depending on the replication method. If you’re using plain WAL replication, not logical replication, the the on-disk order is probably going to be preserved because the basic WAL only contains the resulting storage layer operations, not the higher level queries. Which means that playing the WAL back else where should result in identical on disk results.
Not tried it, but https://github.com/bersler/OpenLogReplicator Might work. Redpanda is easy to set up (kafka-compatible alternative).