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

Extremely slow #125

Open
masylum opened this issue Feb 22, 2024 · 3 comments
Open

Extremely slow #125

masylum opened this issue Feb 22, 2024 · 3 comments

Comments

@masylum
Copy link

masylum commented Feb 22, 2024

I have a bunch of functions, extensions and policies registered and it's impossible to run autogenerate anymore. It looks like there is something going exponential in the compare_registered_entities. the console is going crazy with hundreds of savepoints.

import asyncio
from logging.config import fileConfig
from typing import Any

from alembic import context
from alembic_utils.pg_grant_table import PGGrantTable
from alembic_utils.replaceable_entity import register_entities
from sqlalchemy.engine import Connection
from sqlalchemy.ext.asyncio import create_async_engine

# load all the db models
import core.db.models  # noqa # pyright: ignore
from core.db.metadata import metadata
from core.db.url import db_url
from alembic_utils.pg_policy import PGPolicy
from alembic_utils.pg_function import PGFunction
from alembic_utils.pg_extension import PGExtension

extensions = [
    PGExtension(schema="public", signature="unaccent"),
    PGExtension(schema="public", signature="vector"),
]

functions = [
    PGFunction(
        schema="public",
        signature="i_unaccent(text)",
        definition="RETURNS text LANGUAGE sql IMMUTABLE PARALLEL SAFE AS $func$ SELECT public.unaccent('public.unaccent', $1) $func$",
    ),
    PGFunction(
        schema="public",
        signature="lang_to_regconfig(text)",
        definition="RETURNS regconfig LANGUAGE sql IMMUTABLE STRICT AS $$ SELECT $1::regconfig; $$",
    ),
    PGFunction(
        schema="public",
        signature="user_id()",
        definition="RETURNS text as $$ SELECT nullif(current_setting('request.jwt.claim.sub', true), ''); $$ language sql stable;",
    ),
]

policies = [
    PGPolicy(
        schema="public",
        signature="recent_publishers_private",
        on_entity="public.recent_publishers",
        definition="as PERMISSIVE for ALL to authenticated using (( SELECT (auth.uid())::text AS uid) = (user_id)::text) with check (( SELECT (auth.uid())::text AS uid) = (user_id)::text)",
    ),
    PGPolicy(
        schema="public",
        signature="publishers_authenticated",
        on_entity="public.publishers",
        definition="as PERMISSIVE for ALL to authenticated using (true)",
    ),
    PGPolicy(
        schema="public",
        signature="replicache_view_records_authenticated",
        on_entity="public.replicache_view_records",
        definition="as PERMISSIVE for ALL to authenticated using (true)",
    ),
    PGPolicy(
        schema="public",
        signature="bookmarks_authenticated",
        on_entity="public.bookmarks",
        definition="as PERMISSIVE for ALL to authenticated using ((( SELECT (auth.uid())::character varying AS uid))::text = (user_id)::text) with check ((( SELECT (auth.uid())::character varying AS uid))::text = (user_id)::text)",
    ),
    PGPolicy(
        schema="public",
        signature="stories_authenticated",
        on_entity="public.stories",
        definition="as PERMISSIVE for ALL to authenticated using (true)",
    ),
    PGPolicy(
        schema="public",
        signature="subscriptions_private",
        on_entity="public.subscriptions",
        definition="as PERMISSIVE for ALL to authenticated using (( SELECT (auth.uid())::text AS uid) = (user_id)::text) with check (( SELECT (auth.uid())::text AS uid) = (user_id)::text)",
    ),
    PGPolicy(
        schema="public",
        signature="alembic_version_authenticated",
        on_entity="public.alembic_version",
        definition="as PERMISSIVE for ALL to authenticated using (true)",
    ),
    PGPolicy(
        schema="public",
        signature="replicache_client_groups_private",
        on_entity="public.replicache_client_groups",
        definition="as PERMISSIVE for ALL to authenticated using (( SELECT (auth.uid())::text AS uid) = (user_id)::text) with check (( SELECT (auth.uid())::text AS uid) = (user_id)::text)",
    ),
    PGPolicy(
        schema="public",
        signature="replicache_clients_authenticated",
        on_entity="public.replicache_clients",
        definition="as PERMISSIVE for ALL to authenticated using (true)",
    ),
    PGPolicy(
        schema="public",
        signature="replicache_views_authenticated",
        on_entity="public.replicache_views",
        definition="as PERMISSIVE for ALL to authenticated using (true)",
    ),
    PGPolicy(
        schema="public",
        signature="users_public",
        on_entity="public.users",
        definition="as PERMISSIVE for ALL to public using (true)",
    ),
]

# this is the Alembic Config object, which provides
# access to the values within the .ini file in use.
config = context.config

register_entities(entities=functions + extensions + policies)

if config.config_file_name is not None:
    fileConfig(config.config_file_name)


config.set_main_option("sqlalchemy.url", db_url())


def include_object(
    object: Any, name: Any, type_: Any, reflected: Any, compare_to: Any
) -> bool:
    if isinstance(object, PGGrantTable):
        return False
    return True


def run_migrations_offline() -> None:
    """Run migrations in 'offline' mode.
    This configures the context with just a URL
    and not an Engine, though an Engine is acceptable
    here as well.  By skipping the Engine creation
    we don't even need a DBAPI to be available.
    Calls to context.execute() here emit the given string to the
    script output.
    """
    context.configure(
        url=db_url(),
        target_metadata=metadata,
        literal_binds=True,
        compare_type=True,
        dialect_opts={"paramstyle": "named"},
        include_object=include_object,
    )

    with context.begin_transaction():
        context.run_migrations()


def do_run_migrations(connection: Connection) -> None:
    context.configure(
        connection=connection,
        target_metadata=metadata,
        include_object=include_object,
    )

    with context.begin_transaction():
        context.run_migrations()


async def run_migrations_online():
    """Run migrations in 'online' mode.
    In this scenario we need to create an Engine
    and associate a connection with the context.
    """
    connectable = create_async_engine(db_url(), echo=True, future=True)

    async with connectable.connect() as connection:
        await connection.run_sync(do_run_migrations)


if context.is_offline_mode():
    run_migrations_offline()
else:
    asyncio.run(run_migrations_online())
@olirice
Copy link
Owner

olirice commented Feb 22, 2024

The savepoints are produced during dependency resolution and simulating entities to "diff" them. It admittedly is a lot more than you'd expect but they're light-weight and do not grow exponentially. The benefit of that approach is that you don't have to explicitly declare dependencies among entities.

The rationale for this approach is that --autogenerate doesn't really need to be fast

For context, a 400-ish entity project with lots of dependences among entities completes in < 1 minute.

Are you seeing slow (> 1 minute) runtimes, or just concerned about the savepoint logs?

@gregyannett
Copy link

gregyannett commented Feb 29, 2024

I have also noticed that using alembic_utils greatly slows down my autogenerate times. I use PGTriggers to create triggers that refresh updated_at columns in all of my tables (currently around 150 tables) and autogenerate takes over 15 minutes to run. Creating these triggers in a revision file is much faster

@olirice
Copy link
Owner

olirice commented Feb 29, 2024

I'm getting 71 seconds for a 150 trigger project with postgres running locally in docker. If you're seeing 15 minutes please open an issue with a reproducible example and I'd be happy to help debug

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants