Reduce Supabase disk IO
This commit is contained in:
@@ -49,6 +49,22 @@ def _select_exact_user_id(payload: object, email: str) -> str:
|
||||
def _lookup_user_id_by_email(email: str) -> str:
|
||||
from src.payments.contract_checkout import PAYMENT_CHECKOUT
|
||||
|
||||
normalized_email = str(email or "").strip().lower()
|
||||
profile_rows = PAYMENT_CHECKOUT._rest( # noqa: SLF001
|
||||
"GET",
|
||||
"profiles",
|
||||
params={
|
||||
"select": "id",
|
||||
"email": f"eq.{normalized_email}",
|
||||
"limit": "1",
|
||||
},
|
||||
allowed_status=[200],
|
||||
)
|
||||
if isinstance(profile_rows, list) and profile_rows:
|
||||
user_id = str((profile_rows[0] or {}).get("id") or "").strip()
|
||||
if user_id:
|
||||
return user_id
|
||||
|
||||
payload = PAYMENT_CHECKOUT._auth_admin_request( # noqa: SLF001
|
||||
"GET",
|
||||
f"/admin/users?email={email}",
|
||||
@@ -117,7 +133,7 @@ def main() -> int:
|
||||
"GET",
|
||||
"subscriptions",
|
||||
params={
|
||||
"select": "id,expires_at,status,plan_code,starts_at,source,created_at",
|
||||
"select": "id,plan_code,source,starts_at,expires_at",
|
||||
"user_id": f"eq.{user_id}",
|
||||
"status": "eq.active",
|
||||
"order": "expires_at.desc",
|
||||
@@ -169,37 +185,38 @@ def main() -> int:
|
||||
expires_at = starts_at + timedelta(days=days)
|
||||
|
||||
if isinstance(upcoming, dict) and str(upcoming.get("id") or "").strip():
|
||||
updated = PAYMENT_CHECKOUT._rest( # noqa: SLF001
|
||||
subscription_payload = {
|
||||
"starts_at": starts_at.isoformat(),
|
||||
"expires_at": expires_at.isoformat(),
|
||||
"updated_at": now.isoformat(),
|
||||
}
|
||||
PAYMENT_CHECKOUT._rest( # noqa: SLF001
|
||||
"PATCH",
|
||||
"subscriptions",
|
||||
params={"id": f"eq.{upcoming['id']}"},
|
||||
payload={
|
||||
"starts_at": starts_at.isoformat(),
|
||||
"expires_at": expires_at.isoformat(),
|
||||
"updated_at": now.isoformat(),
|
||||
},
|
||||
prefer="return=representation",
|
||||
payload=subscription_payload,
|
||||
prefer="return=minimal",
|
||||
allowed_status=[200],
|
||||
)
|
||||
subscription = updated[0] if isinstance(updated, list) and updated else {}
|
||||
subscription = {**upcoming, **subscription_payload}
|
||||
else:
|
||||
created = PAYMENT_CHECKOUT._rest( # noqa: SLF001
|
||||
subscription = {
|
||||
"user_id": user_id,
|
||||
"plan_code": plan_code,
|
||||
"status": "active",
|
||||
"starts_at": starts_at.isoformat(),
|
||||
"expires_at": expires_at.isoformat(),
|
||||
"source": actor,
|
||||
"created_at": now.isoformat(),
|
||||
"updated_at": now.isoformat(),
|
||||
}
|
||||
PAYMENT_CHECKOUT._rest( # noqa: SLF001
|
||||
"POST",
|
||||
"subscriptions",
|
||||
payload={
|
||||
"user_id": user_id,
|
||||
"plan_code": plan_code,
|
||||
"status": "active",
|
||||
"starts_at": starts_at.isoformat(),
|
||||
"expires_at": expires_at.isoformat(),
|
||||
"source": actor,
|
||||
"created_at": now.isoformat(),
|
||||
"updated_at": now.isoformat(),
|
||||
},
|
||||
prefer="return=representation",
|
||||
payload=subscription,
|
||||
prefer="return=minimal",
|
||||
allowed_status=[201],
|
||||
)
|
||||
subscription = created[0] if isinstance(created, list) and created else {}
|
||||
|
||||
PAYMENT_CHECKOUT._rest( # noqa: SLF001
|
||||
"POST",
|
||||
@@ -219,7 +236,7 @@ def main() -> int:
|
||||
},
|
||||
"created_at": now.isoformat(),
|
||||
},
|
||||
prefer="return=representation",
|
||||
prefer="return=minimal",
|
||||
allowed_status=[201],
|
||||
)
|
||||
SUPABASE_ENTITLEMENT.invalidate_subscription_cache(user_id)
|
||||
|
||||
@@ -50,31 +50,48 @@ def _list_recent_intents(user_id: str, limit: int = 20) -> List[Dict[str, Any]]:
|
||||
|
||||
|
||||
def _find_intent_by_tx(user_id: str, tx_hash: str) -> Optional[Dict[str, Any]]:
|
||||
rows = _list_recent_intents(user_id, limit=50)
|
||||
tx_norm = str(tx_hash or "").strip().lower()
|
||||
for row in rows:
|
||||
if str(row.get("tx_hash") or "").strip().lower() == tx_norm:
|
||||
return row
|
||||
return None
|
||||
|
||||
|
||||
def _find_intent_by_order_id(user_id: str, order_id_hex: str) -> Optional[Dict[str, Any]]:
|
||||
if not tx_norm:
|
||||
return None
|
||||
rows = PAYMENT_CHECKOUT._rest(
|
||||
"GET",
|
||||
"payment_intents",
|
||||
params={
|
||||
"select": (
|
||||
"id,user_id,plan_code,status,tx_hash,order_id_hex,"
|
||||
"amount_units,token_address,created_at,updated_at"
|
||||
),
|
||||
"user_id": f"eq.{user_id}",
|
||||
"order_id_hex": f"eq.{order_id_hex.lower()}",
|
||||
"order": "created_at.desc",
|
||||
"limit": "5",
|
||||
"select": "id,user_id",
|
||||
"tx_hash": f"eq.{tx_norm}",
|
||||
"limit": "1",
|
||||
},
|
||||
allowed_status=[200],
|
||||
)
|
||||
return rows[0] if isinstance(rows, list) and rows else None
|
||||
row = rows[0] if isinstance(rows, list) and rows else None
|
||||
if not isinstance(row, dict):
|
||||
return None
|
||||
if str(row.get("user_id") or "").strip() != str(user_id or "").strip():
|
||||
return None
|
||||
return row
|
||||
|
||||
|
||||
def _find_intent_by_order_id(user_id: str, order_id_hex: str) -> Optional[Dict[str, Any]]:
|
||||
normalized_user_id = str(user_id or "").strip()
|
||||
normalized_order_id = str(order_id_hex or "").strip().lower()
|
||||
if not normalized_user_id or not normalized_order_id:
|
||||
return None
|
||||
rows = PAYMENT_CHECKOUT._rest(
|
||||
"GET",
|
||||
"payment_intents",
|
||||
params={
|
||||
"select": "id,user_id",
|
||||
"order_id_hex": f"eq.{normalized_order_id}",
|
||||
"limit": "1",
|
||||
},
|
||||
allowed_status=[200],
|
||||
)
|
||||
row = rows[0] if isinstance(rows, list) and rows else None
|
||||
if not isinstance(row, dict):
|
||||
return None
|
||||
if str(row.get("user_id") or "").strip() != normalized_user_id:
|
||||
return None
|
||||
return row
|
||||
|
||||
|
||||
def _decode_pay_call(tx_hash: str) -> Optional[Dict[str, Any]]:
|
||||
|
||||
@@ -16,6 +16,21 @@ def _lookup_user_id_by_email(email: str) -> str:
|
||||
from src.payments.contract_checkout import PAYMENT_CHECKOUT, PaymentCheckoutError
|
||||
|
||||
normalized_email = str(email or "").strip().lower()
|
||||
profile_rows = PAYMENT_CHECKOUT._rest( # noqa: SLF001
|
||||
"GET",
|
||||
"profiles",
|
||||
params={
|
||||
"select": "id",
|
||||
"email": f"eq.{normalized_email}",
|
||||
"limit": "1",
|
||||
},
|
||||
allowed_status=[200],
|
||||
)
|
||||
if isinstance(profile_rows, list) and profile_rows:
|
||||
user_id = str((profile_rows[0] or {}).get("id") or "").strip()
|
||||
if user_id:
|
||||
return user_id
|
||||
|
||||
payload = PAYMENT_CHECKOUT._auth_admin_request( # noqa: SLF001
|
||||
"GET",
|
||||
f"/admin/users?email={email}",
|
||||
|
||||
@@ -0,0 +1,98 @@
|
||||
-- Supabase Disk IO diagnostics for PolyWeather.
|
||||
-- Run sections independently if your project does not expose every stats view.
|
||||
|
||||
-- 1) Tables with high sequential scan pressure.
|
||||
select
|
||||
schemaname,
|
||||
relname,
|
||||
seq_scan,
|
||||
seq_tup_read,
|
||||
idx_scan,
|
||||
n_tup_ins,
|
||||
n_tup_upd,
|
||||
n_tup_del,
|
||||
n_dead_tup
|
||||
from pg_stat_user_tables
|
||||
where schemaname = 'public'
|
||||
order by seq_tup_read desc
|
||||
limit 30;
|
||||
|
||||
-- 2) Largest public tables and indexes.
|
||||
select
|
||||
relname,
|
||||
pg_size_pretty(pg_total_relation_size(relid)) as total_size,
|
||||
pg_size_pretty(pg_relation_size(relid)) as table_size,
|
||||
pg_size_pretty(pg_indexes_size(relid)) as index_size
|
||||
from pg_catalog.pg_statio_user_tables
|
||||
where schemaname = 'public'
|
||||
order by pg_total_relation_size(relid) desc
|
||||
limit 30;
|
||||
|
||||
-- 3) Slow/high-read statements, if pg_stat_statements is available.
|
||||
select
|
||||
calls,
|
||||
round(total_exec_time::numeric, 2) as total_exec_ms,
|
||||
round(mean_exec_time::numeric, 2) as mean_exec_ms,
|
||||
rows,
|
||||
shared_blks_read,
|
||||
shared_blks_hit,
|
||||
temp_blks_read,
|
||||
temp_blks_written,
|
||||
left(query, 500) as query_sample
|
||||
from pg_stat_statements
|
||||
order by shared_blks_read desc, total_exec_time desc
|
||||
limit 30;
|
||||
|
||||
-- 4) Dead tuple pressure that can trigger heavier autovacuum work.
|
||||
select
|
||||
schemaname,
|
||||
relname,
|
||||
n_live_tup,
|
||||
n_dead_tup,
|
||||
last_vacuum,
|
||||
last_autovacuum,
|
||||
last_analyze,
|
||||
last_autoanalyze
|
||||
from pg_stat_user_tables
|
||||
where schemaname = 'public'
|
||||
order by n_dead_tup desc
|
||||
limit 30;
|
||||
|
||||
-- 5) Indexes that still read the most disk blocks.
|
||||
select
|
||||
s.schemaname,
|
||||
s.relname,
|
||||
s.indexrelname,
|
||||
s.idx_scan,
|
||||
s.idx_tup_read,
|
||||
s.idx_tup_fetch,
|
||||
io.idx_blks_read,
|
||||
io.idx_blks_hit,
|
||||
pg_size_pretty(pg_relation_size(s.indexrelid)) as index_size
|
||||
from pg_stat_user_indexes s
|
||||
join pg_statio_user_indexes io
|
||||
on io.indexrelid = s.indexrelid
|
||||
where s.schemaname = 'public'
|
||||
order by io.idx_blks_read desc, s.idx_scan desc
|
||||
limit 50;
|
||||
|
||||
-- 6) Large non-unique indexes with no recorded scans since stats reset.
|
||||
-- Treat this as a candidate list only; confirm with production traffic history
|
||||
-- before dropping anything.
|
||||
select
|
||||
s.schemaname,
|
||||
s.relname,
|
||||
s.indexrelname,
|
||||
s.idx_scan,
|
||||
pg_size_pretty(pg_relation_size(s.indexrelid)) as index_size,
|
||||
i.indisunique,
|
||||
i.indisprimary
|
||||
from pg_stat_user_indexes s
|
||||
join pg_index i
|
||||
on i.indexrelid = s.indexrelid
|
||||
where s.schemaname = 'public'
|
||||
and s.idx_scan = 0
|
||||
and not i.indisprimary
|
||||
and not i.indisunique
|
||||
order by pg_relation_size(s.indexrelid) desc
|
||||
limit 30;
|
||||
@@ -0,0 +1,89 @@
|
||||
-- PolyWeather Supabase Disk IO mitigation indexes.
|
||||
-- Run this in the Supabase SQL Editor for an existing production project.
|
||||
|
||||
drop index if exists public.idx_subscriptions_user_status_expiry;
|
||||
create index if not exists idx_subscriptions_user_status_expiry
|
||||
on public.subscriptions(user_id, expires_at desc)
|
||||
include (id, starts_at, plan_code, source)
|
||||
where status = 'active';
|
||||
|
||||
drop index if exists public.idx_profiles_email;
|
||||
create index if not exists idx_profiles_email
|
||||
on public.profiles(email)
|
||||
include (id);
|
||||
|
||||
drop index if exists public.idx_profiles_id_lookup;
|
||||
create index if not exists idx_profiles_id_lookup
|
||||
on public.profiles(id)
|
||||
include (email, created_at);
|
||||
|
||||
drop index if exists public.idx_subscriptions_status_expiry;
|
||||
create index if not exists idx_subscriptions_status_expiry
|
||||
on public.subscriptions(expires_at asc)
|
||||
include (user_id, starts_at, plan_code)
|
||||
where status = 'active';
|
||||
|
||||
drop index if exists public.idx_subscriptions_user_created;
|
||||
create index if not exists idx_subscriptions_user_created
|
||||
on public.subscriptions(user_id, created_at desc)
|
||||
include (id, status, plan_code, source, starts_at, expires_at, updated_at);
|
||||
|
||||
drop index if exists public.idx_payments_created_at;
|
||||
create index if not exists idx_payments_created_at
|
||||
on public.payments(created_at desc)
|
||||
include (id, user_id, amount, currency, chain, tx_hash, status);
|
||||
|
||||
drop index if exists public.idx_user_wallets_user_chain;
|
||||
create index if not exists idx_user_wallets_user_chain
|
||||
on public.user_wallets(user_id, chain_id, is_primary desc, verified_at desc)
|
||||
include (id, address)
|
||||
where status = 'active';
|
||||
|
||||
drop index if exists public.idx_user_wallets_chain_address_owner;
|
||||
create index if not exists idx_user_wallets_chain_address_owner
|
||||
on public.user_wallets(chain_id, address)
|
||||
include (user_id, status);
|
||||
|
||||
drop index if exists public.idx_wallet_link_challenges_lookup;
|
||||
|
||||
drop index if exists public.idx_payment_intents_user_status;
|
||||
|
||||
drop index if exists public.idx_payment_intents_status_updated;
|
||||
create index if not exists idx_payment_intents_status_updated
|
||||
on public.payment_intents(status, updated_at desc)
|
||||
include (user_id)
|
||||
where status in ('submitted', 'confirmed');
|
||||
|
||||
create index if not exists idx_payment_intents_user_status_updated
|
||||
on public.payment_intents(user_id, status, updated_at desc);
|
||||
|
||||
drop index if exists public.idx_payment_intents_submitted_tx_updated;
|
||||
create index if not exists idx_payment_intents_submitted_tx_updated
|
||||
on public.payment_intents(updated_at asc)
|
||||
include (id, user_id, tx_hash, chain_id)
|
||||
where status = 'submitted' and tx_hash is not null;
|
||||
|
||||
create index if not exists idx_payment_intents_user_created
|
||||
on public.payment_intents(user_id, created_at desc);
|
||||
|
||||
drop index if exists public.idx_payment_intents_tx_hash;
|
||||
create index if not exists idx_payment_intents_tx_hash
|
||||
on public.payment_intents(tx_hash)
|
||||
include (id, user_id)
|
||||
where tx_hash is not null;
|
||||
|
||||
create index if not exists idx_payment_transactions_intent
|
||||
on public.payment_transactions(intent_id, created_at desc);
|
||||
|
||||
drop index if exists public.idx_payment_transactions_tx_hash_intent;
|
||||
create index if not exists idx_payment_transactions_tx_hash_intent
|
||||
on public.payment_transactions(tx_hash)
|
||||
include (intent_id);
|
||||
|
||||
analyze public.subscriptions;
|
||||
analyze public.profiles;
|
||||
analyze public.payments;
|
||||
analyze public.user_wallets;
|
||||
analyze public.wallet_link_challenges;
|
||||
analyze public.payment_intents;
|
||||
analyze public.payment_transactions;
|
||||
@@ -12,6 +12,14 @@ create table if not exists public.profiles (
|
||||
updated_at timestamptz not null default now()
|
||||
);
|
||||
|
||||
create index if not exists idx_profiles_email
|
||||
on public.profiles(email)
|
||||
include (id);
|
||||
|
||||
create index if not exists idx_profiles_id_lookup
|
||||
on public.profiles(id)
|
||||
include (email, created_at);
|
||||
|
||||
create table if not exists public.subscriptions (
|
||||
id bigserial primary key,
|
||||
user_id uuid not null references auth.users(id) on delete cascade,
|
||||
@@ -25,7 +33,18 @@ create table if not exists public.subscriptions (
|
||||
);
|
||||
|
||||
create index if not exists idx_subscriptions_user_status_expiry
|
||||
on public.subscriptions(user_id, status, expires_at desc);
|
||||
on public.subscriptions(user_id, expires_at desc)
|
||||
include (id, starts_at, plan_code, source)
|
||||
where status = 'active';
|
||||
|
||||
create index if not exists idx_subscriptions_status_expiry
|
||||
on public.subscriptions(expires_at asc)
|
||||
include (user_id, starts_at, plan_code)
|
||||
where status = 'active';
|
||||
|
||||
create index if not exists idx_subscriptions_user_created
|
||||
on public.subscriptions(user_id, created_at desc)
|
||||
include (id, status, plan_code, source, starts_at, expires_at, updated_at);
|
||||
|
||||
create table if not exists public.payments (
|
||||
id bigserial primary key,
|
||||
@@ -40,6 +59,10 @@ create table if not exists public.payments (
|
||||
updated_at timestamptz not null default now()
|
||||
);
|
||||
|
||||
create index if not exists idx_payments_created_at
|
||||
on public.payments(created_at desc)
|
||||
include (id, user_id, amount, currency, chain, tx_hash, status);
|
||||
|
||||
create table if not exists public.entitlement_events (
|
||||
id bigserial primary key,
|
||||
user_id uuid not null references auth.users(id) on delete cascade,
|
||||
@@ -65,7 +88,13 @@ create table if not exists public.user_wallets (
|
||||
);
|
||||
|
||||
create index if not exists idx_user_wallets_user_chain
|
||||
on public.user_wallets(user_id, chain_id, status, is_primary desc, verified_at desc);
|
||||
on public.user_wallets(user_id, chain_id, is_primary desc, verified_at desc)
|
||||
include (id, address)
|
||||
where status = 'active';
|
||||
|
||||
create index if not exists idx_user_wallets_chain_address_owner
|
||||
on public.user_wallets(chain_id, address)
|
||||
include (user_id, status);
|
||||
|
||||
create table if not exists public.wallet_link_challenges (
|
||||
id bigserial primary key,
|
||||
@@ -79,9 +108,6 @@ create table if not exists public.wallet_link_challenges (
|
||||
created_at timestamptz not null default now()
|
||||
);
|
||||
|
||||
create index if not exists idx_wallet_link_challenges_lookup
|
||||
on public.wallet_link_challenges(user_id, chain_id, address, nonce, created_at desc);
|
||||
|
||||
create table if not exists public.payment_intents (
|
||||
id uuid primary key default gen_random_uuid(),
|
||||
user_id uuid not null references auth.users(id) on delete cascade,
|
||||
@@ -103,11 +129,26 @@ create table if not exists public.payment_intents (
|
||||
updated_at timestamptz not null default now()
|
||||
);
|
||||
|
||||
create index if not exists idx_payment_intents_user_status
|
||||
on public.payment_intents(user_id, status, created_at desc);
|
||||
create index if not exists idx_payment_intents_status_updated
|
||||
on public.payment_intents(status, updated_at desc)
|
||||
include (user_id)
|
||||
where status in ('submitted', 'confirmed');
|
||||
|
||||
create index if not exists idx_payment_intents_user_status_updated
|
||||
on public.payment_intents(user_id, status, updated_at desc);
|
||||
|
||||
create index if not exists idx_payment_intents_submitted_tx_updated
|
||||
on public.payment_intents(updated_at asc)
|
||||
include (id, user_id, tx_hash, chain_id)
|
||||
where status = 'submitted' and tx_hash is not null;
|
||||
|
||||
create index if not exists idx_payment_intents_user_created
|
||||
on public.payment_intents(user_id, created_at desc);
|
||||
|
||||
create index if not exists idx_payment_intents_tx_hash
|
||||
on public.payment_intents(tx_hash);
|
||||
on public.payment_intents(tx_hash)
|
||||
include (id, user_id)
|
||||
where tx_hash is not null;
|
||||
|
||||
create table if not exists public.payment_transactions (
|
||||
id bigserial primary key,
|
||||
@@ -128,6 +169,10 @@ create table if not exists public.payment_transactions (
|
||||
create index if not exists idx_payment_transactions_intent
|
||||
on public.payment_transactions(intent_id, created_at desc);
|
||||
|
||||
create index if not exists idx_payment_transactions_tx_hash_intent
|
||||
on public.payment_transactions(tx_hash)
|
||||
include (intent_id);
|
||||
|
||||
create or replace function public.sync_profile_from_auth()
|
||||
returns trigger
|
||||
language plpgsql
|
||||
|
||||
Reference in New Issue
Block a user