Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: prevent multiple pvm errors on migration #31332

Open
wants to merge 6 commits into
base: master
Choose a base branch
from

Conversation

eschutho
Copy link
Member

@eschutho eschutho commented Dec 6, 2024

SUMMARY

We're seeing some errors when running the catalog migration because of some PVMs that already exist. In that case, I'm not going to update the PVM.

BEFORE/AFTER SCREENSHOTS OR ANIMATED GIF

TESTING INSTRUCTIONS

ADDITIONAL INFORMATION

  • Has associated issue:
  • Required feature flags:
  • Changes UI
  • Includes DB Migration (follow approval process in SIP-59)
    • Migration is atomic, supports rollback & is backwards-compatible
    • Confirm DB migration upgrade and downgrade tested
    • Runtime estimates and downtime expectations provided
  • Introduces new feature or API
  • Removes existing feature or API

@github-actions github-actions bot added the risk:db-migration PRs that require a DB migration label Dec 6, 2024
@dosubot dosubot bot added the change:backend Requires changing the backend label Dec 6, 2024
@@ -45,10 +49,21 @@ def upgrade():
"slices",
sa.Column("catalog_perm", sa.String(length=1000), nullable=True),
)

if not table_has_index(perm_table, perm_index_view_menu):
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

might be good to add these into a helper function call create_index that will do the check and add it like here #31303

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice, I was thinking of a helper function. I don't want to create a conflict with that PR. Perhaps we can use his helper function after it's merged.

@eschutho eschutho changed the title Elizabeth/add indexes perms fix: prevent deadlocks on catalogs migration Dec 7, 2024
@eschutho eschutho force-pushed the elizabeth/add-indexes-perms branch from 069b0fa to fc45193 Compare December 7, 2024 00:35
"New permission %s already exists. Removing old one",
new_perm,
)
session.delete(existing_pvm)
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@betodealmeida not sure if you would prefer to return pvms to be deleted instead of deleting them here

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we really need to delete anything here? Should we just rename the existing ones and create new ones for new catalogs? The old ones are attached to roles already and we probably want to maintain those associations.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@mistercrunch the current code does that, we get the current perm (where the catalog name is null, see how we compute current_perm in line 492) and change it to point to the new perm name new_perm, which includes the default catalog name (line 497). Then we simply rename the existing PVM to have the new name, so that existing associations like roles are preserved (line 508 on the left).

In regards to this PR, I don't think we should delete existing PVMs. First, because they should not exist... the only reason I can imagine why it would exist is bad upgrade-downgrade that didn't rename the PVMs correctly. If that happens, I think it's better to just keep the existing PVM, since it already has the correct name, and might be associated with roles.

if table_has_index(perm_table, perm_index_permission_id):
op.drop_index(op.f(perm_index_permission_id), table_name=perm_table)
if table_has_index(perm_table, perm_index_view_menu):
op.drop_index(op.f(perm_index_view_menu), table_name=perm_table)
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@betodealmeida any thoughts whether you think this change should stay with the new migration or in a different PR? My thought is to keep them together so that tables don't get out of sync, but I also see the benefit of splitting them up.

__table_args__ = (UniqueConstraint("permission_id", "view_menu_id"),)
__table_args__ = (
sqla.Index("idx_permission_view_menu_id", "view_menu_id"),
sqla.Index("idx_permission_permission_id", "permission_id"),
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

haven't looked at the queries against that table, but could be that a compound index work well here. I saw some queries that predicate on view_menu_id and some on both view_menu_id and permission_id. If permission is never predicated by itself, the compound on view_menu_id and permission_id (in that order) would cover those

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Adding more info here, FAB already has a compound index since it defines a unique constraint on these FK: https://github.com/preset-io/Flask-AppBuilder/blob/master/flask_appbuilder/security/sqla/models.py#L73

Screenshot 2024-12-09 at 10 36 03

Checked the security converge code, and we have the following queries:

session.query(ViewMenu).filter(ViewMenu.name == view_name).one_or_none()
session.query(Permission).filter(Permission.name == permission_name).one_or_none()
session.query(PermissionView)
        .filter(
            PermissionView.view_menu_id == view_menu.id,
            PermissionView.permission_id == permission.id,
        )
        session.query(PermissionView)
        .join(Permission)
        .join(ViewMenu)
        .filter(ViewMenu.name == view_name, Permission.name == permission_name)
            session.query(PermissionView)
            .join(Permission)
            .filter(Permission.name == old_permission_name)
            session.query(PermissionView)
            .join(ViewMenu)
            .filter(ViewMenu.name == old_view_name)

We have indexes to efficiently query all of these:

  • index on ViewMenu.name
  • index on Permission.name
  • Indexes on Permission.id and ViewMenu.id

Yet we can have more eficient joins by adding indexes on the FK for ab_permission_view_role and ab_permission_view
dpgaspar/Flask-AppBuilder#2293

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks @dpgaspar! I'll remove the changes here and we'll bring them in from FAB then.

"New permission %s already exists. Removing old one",
new_perm,
)
session.delete(existing_pvm)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@mistercrunch the current code does that, we get the current perm (where the catalog name is null, see how we compute current_perm in line 492) and change it to point to the new perm name new_perm, which includes the default catalog name (line 497). Then we simply rename the existing PVM to have the new name, so that existing associations like roles are preserved (line 508 on the left).

In regards to this PR, I don't think we should delete existing PVMs. First, because they should not exist... the only reason I can imagine why it would exist is bad upgrade-downgrade that didn't rename the PVMs correctly. If that happens, I think it's better to just keep the existing PVM, since it already has the correct name, and might be associated with roles.

"New permission %s already exists. Deleting old one",
new_name,
)
pvms_to_delete.append(pvm)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same here, I would just leave the existing one, something like:

            # check to see if the new name already exists
            if not session.query(ViewMenu).filter_by(name=new_name).one_or_none():
                # new perm doesn't exist, rename existing one to the new name
                pvms_to_rename.append((pvm, new_name))

@mistercrunch
Copy link
Member

Yes, deleting is yet more potential locks. I also think there are methods/CLI commands that will clean up perms that should not exist. I forgot whether it's a fab feature or not or what triggers it, but may even be on app startup (?)

@eschutho eschutho changed the title fix: prevent deadlocks on catalogs migration fix: prevent multiple pvm errors on migration Dec 10, 2024
@eschutho eschutho removed the risk:db-migration PRs that require a DB migration label Dec 10, 2024
Copy link
Member

@mistercrunch mistercrunch left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
change:backend Requires changing the backend preset-io size/XS
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants