Repository navigation
Rewrite large object migration as a direct relation copy. - #63
Open
ibrarahmad wants to merge 1 commit into
Open
ibrarahmad wants to merge 1 commit into
ibrarahmad wants to merge 1 commit into
Conversation
Reverse migration rewrote every object through the large object API, on the premise that inserting into pg_catalog.pg_largeobject was not allowed. It is, for the superuser both migration functions already require. Both directions become a tuple copy between the two relations, in C: pages are copied verbatim so sparse objects stay sparse, ownership and ACLs are recorded in pg_shdepend rather than set by a raw catalog UPDATE that DROP ROLE could not see, comments survive the round trip in a parking table, native objects are removed through the same deletion path DROP uses, and the layouts are verified against the running server rather than assumed. Migration no longer goes through the renamed _orig functions, so migrate_to_native() works while lolor is disabled. The copy holds ShareRowExclusiveLock on both stores until commit. Ordinary large object access takes RowExclusiveLock, which does not conflict with itself, so a concurrent lo_write() could otherwise commit between the copy of a page and the emptying of the source store and be lost with no error. Each row gets its own memory context, so millions of pages do not exhaust the backend. Migration refuses up front while any object refers to a dropped role, instead of dying part-way with "role N was concurrently dropped"; it refuses while other sessions are connected or when run from anything but a client session; and migrate_from_native(peer_oids) turns the documented cross-node OID collision into a pre-flight refusal, with lolor.digest() and lolor.native_lo_oids() for checking convergence.
|
Warning Review limit reached
This review includes 12 billable files and costs up to $3.00. Or wait 59 minutes for your next included review. View limit detailsLimit details: You’ve used the included review currently available. Review configuration: ⚙️ Run configuration
⛔ Files ignored due to path filters (1)
📒 Files selected for processing (12)
Comment |
This was referenced Oct 9, 2026
Up to standards ✅🟢 Issues
|
| Category | Results |
|---|---|
| Compatibility | 16 high (4 false positives) |
| Complexity | 5 medium |
🟢 Metrics 68 complexity · 2 duplication
Metric Results Complexity 68 Duplication 2
NEW Get contextual insights on your PRs based on Codacy's metrics, along with PR and Jira context, without leaving GitHub. Enable AI reviewer
TIP This summary will be updated as you push new changes.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
lolor moves large objects between PostgreSQL's native storage and its own tables with
lolor.migrate_from_native()andlolor.migrate_to_native(). The move back to native storage is written in plpgsql and rebuilds each object through the large object API, onelo_lseek64()and onelowrite()call per 2 KB page, because the original commit believed rows cannot be inserted intopg_catalog.pg_largeobjectdirectly. A superuser can insert into it, and both functions already require superuser.A 1 GB large object is about 500,000 pages, so moving it back costs about a million function calls. A sparse object, with data at offset 0 and at offset 10 MB and nothing in between, occupies two pages, but the move writes all 5,000 pages in between as zeros, so 4 KB of data becomes 10 MB on disk.
After
migrate_to_native(),DROP ROLE alicesucceeds although alice still owns the migrated objects, andREASSIGN OWNED BY alice TO bobleaves them untouched. Ownership was set with a plainUPDATEon the catalog, which records nothing inpg_shdepend, the table PostgreSQL uses to protect owned objects.COMMENT ON LARGE OBJECT 16385 IS 'invoice scan', thenmigrate_from_native(), thenmigrate_to_native(): the comment is gone.While a migration runs, another session's
lo_write()can commit between the copy of the rows and the removal of the originals, and that write disappears with no error. Both sides takeRowExclusiveLock, which does not conflict with itself.Native large object OIDs are not node encoded. Running
migrate_from_native()on several nodes of a cluster can map different objects onto the same OID, and because the migration runs underspock.repair_mode(), the other nodes never see it happen.How this PR fixes it
lolor.migrate_storage(to_native boolean), in C, copies tuples directly betweenpg_catalog.pg_largeobject{,_metadata}andlolor.pg_largeobject{,_metadata}. The two pairs have the same layout by construction, and the function compares the tuple descriptors against the running server before copying anything. Pages are copied as they are, so a sparse object stays sparse and no per-page API calls are made. Native objects are removed withperformMultipleDeletions(), the pathDROPuses, so their dependencies, comments and security labels go with them. Each row is processed in its own memory context.migrate_to_native()no longer requires lolor to be enabled, andDROP EXTENSION lolorstill migrates objects back through the existing event trigger, now through this copy.On the way to native storage, ownership and ACLs are recorded with
recordDependencyOnOwner()andrecordDependencyOnNewAcl(), soDROP ROLE,REASSIGN OWNEDandDROP OWNEDsee the objects again.Comments are kept in a new table,
lolor.pg_largeobject_description, while an object is in lolor storage and put back withCreateComments()when it returns.lo_unlink()removes the parked row so a reused OID cannot pick it up.Both stores are locked with
ShareRowExclusiveLockuntil the transaction commits, in a fixed order so that two migrations in opposite directions cannot deadlock.Before copying to native storage, every owner and every role in an ACL is checked against
pg_authid; objects that refer to a dropped role are reported with their OIDs and the migration refuses, with a hint tolo_unlink()them, instead of failing part way through withrole N was concurrently dropped. Both functions refuse while another session is connected to the database and when run from anything other than a client session.migrate_from_native(peer_oids oid[])refuses if any native OID on this node is in the list, andlolor.native_lo_oids()andlolor.digest()produce the per-node data for building that list and comparing nodes afterwards.migrate_from_native()also refuses if any large object carries a security label, which lolor storage cannot represent.