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

Stripe FDW randomly returns no data #237

Open
vincenzon opened this issue Feb 8, 2024 · 8 comments · Fixed by #239
Open

Stripe FDW randomly returns no data #237

vincenzon opened this issue Feb 8, 2024 · 8 comments · Fixed by #239
Labels
bug Something isn't working

Comments

@vincenzon
Copy link

I am querying stripe FDW tables using an rpc call using supabase-js from my web app. About half the time, the query returns no results. It should return 2 rows, and does so about half the time. I can reproduce it reliably by just refreshing the page. It is not obvious when it will return results and when not. It may be a function of how quickly I refresh, but isn't always. I can refresh after waiting several minutes, get no results, refresh again and get results as well as vice-versa. There isn't any rhyme or reason to it as far as I can tell. When it returns no results, it does not have an error, just an empty result: {data: [], error: null}.

Supabase support is telling me to use pg_cron and to build what is effectively a materialized view of the stripe FDW data within supabase. If that is the recommended workflow, I think it should be documented as such.

Here is the rpc call with the query, it joins the product and price tables. There are two products, and those were populated once a while ago and have not changed.

 create or replace function public.get_subscription_prices()
returns table (
    name text,
    product_id text,
    price_id text, 
    price real
)
language plpgsql security definer as
$$
#variable_conflict use_column
begin
    set statement_timeout to 60000;
    return QUERY
        select p.name, p.id as product_id, q.id as price_id, (q.unit_amount / 100.0)::real as price
        from stripe.products p
        left join stripe.prices q on p.id = q.product
        where p.active and q.active;
end
$$;
@vincenzon vincenzon added the bug Something isn't working label Feb 8, 2024
@imor
Copy link
Contributor

imor commented Feb 9, 2024

What happens if you run the following query directly in the database:

select p.name, p.id as product_id, q.id as price_id, (q.unit_amount / 100.0)::real as price
from stripe.products p
left join stripe.prices q on p.id = q.product
where p.active and q.active;

Does it also produce results intermittently? What about even simpler queries like:

select q.id, q.unit_amount, q.product, q.active from stripe.prices;

Or

select p.id, p.name, p.active from stripe.products p; 

@vincenzon
Copy link
Author

vincenzon commented Feb 9, 2024 via email

@vincenzon
Copy link
Author

vincenzon commented Feb 9, 2024 via email

@imor
Copy link
Contributor

imor commented Feb 13, 2024

This looks like a bug in wrappers. If I run a query directly, e.g. select * from stripe.products it successfully returns results. But if I wrap the same query in a function, it only returns the result the first time. The difference in these is that the StripeFdw object is created afresh each time select * from stripe.products is run but not when the query is run from inside the function.

@imor
Copy link
Contributor

imor commented Feb 13, 2024

I analyzed further and this bug is more involved than I thought. If the function with the query is called after select * from stripe.products it fails with this error: ERROR: required option 'object' is not specified.

To reproduce run the following:

create or replace function public.get_products()
returns table (
    product_id text
)
language plpgsql as
$$
begin
    return query
    select p.id as product_id from stripe.products p;
end
$$;

select * from public.get_products();
select * from stripe.products;
select * from public.get_products();--ERROR:  required option 'object' is not specified

The error occurs because the code assumes that a foreign table will only ever have one FdwState (see these lines). But this is not true when we have a foreign table query wrapped in a function and a direct query at the same time. While the select statement query always creates a new FdwState, the query in the function does not. This means the direct query is releasing the memory context in which the wrapped query's FdwState was allocated, resulting in a use after free bug. To fix this we need to ensure that a memory context holding an FdwState is not reset.

@burmecia
Copy link
Member

When 1st time calling the function, postgres will cache the execution plan to improve query performance. So for the following calls, the FdwState won't be initialised again and execution plan is reused. This will cause unexpected results as the FdwState is reused without resetting its internal states.

For short-term solution, we can use dynamic query in the function to prevent postgres from using execution plan cache and re-create FdwState each time. For example,

create or replace function public.get_products()
returns table (
    product_id text
)
language plpgsql as
$$
begin
    return query
    execute 'select p.id as product_id from stripe.products p';  -- use dynamic query here
end
$$;

For long-term solution, we might need to find a good way to reuse FdwState during each function call and also don't interfere with other normal queries.

@vincenzon
Copy link
Author

The dynamic query is indeed working. Thank you. For me this is closed, but I'll leave it to you to decide whether to keep it open to track the long-term issue.

Thanks for this, looks like it took some serious work.

@imor
Copy link
Contributor

imor commented Mar 11, 2024

Although we have a good workaround (using a dynamic query), reopening this to track the use-after-free bug that we still need to fix.

@imor imor reopened this Mar 11, 2024
kamyshdm pushed a commit to dymium-io/supabase-wrappers that referenced this issue Jun 17, 2024
[supabase#218] Fixes 218, adds human readable explanation
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working
Projects
None yet
Development

Successfully merging a pull request may close this issue.

3 participants