fix(rds-proxy): route recurring_transaction_rules through :direct (#6062)
## Symptom
Sustained session-pinning on \`lago-production-rails-proxy\` (RDS Proxy
for Postgres) on \`lago-prod-us-1\` — the CloudWatch
\`DatabaseConnectionsCurrentlySessionPinned\` gauge sits at ≈1
constantly, with a trickle of new pin events all reasoned as \`Reason:
The connection ran a SQL query which exceeded the 16384 byte limit.\`
## Root cause
\`Wallets::CreateIntervalWalletTransactionsService#recurring_transaction_rules\`
builds a raw-SQL CTE
(\`app/services/wallets/create_interval_wallet_transactions_service.rb:44-78\`)
that unions 5 anniversary sub-queries (weekly / monthly / quarterly /
semiannual / yearly) plus a \`wallet_transactions\` "already applied
today" CTE, each doing timezone-aware date math across
\`recurring_transaction_rules × wallets × customers ×
billing_entities\`.
The rendered query template is **17,726 bytes** — over RDS Proxy's 16 KB
per-statement pin threshold before any parameters are even bound. Every
execution goes over the wire → session-pins the backend connection.
Verified via \`pg_stat_statements\` on \`lago-production\`
(post-\`pg_stat_statements_reset()\`):
\`\`\`
q_len=17726 calls=1 mean_ms=140.8 WITH pending_recurring_rules AS ( --
Anniversary rules ...
\`\`\`
## Fix
Route the \`find_by_sql\` through \`ApplicationRecord.connected_to(role:
:direct)\` so it bypasses the pooler. Same one-line pattern PR #6029
applied to
\`Subscriptions::OrganizationBillingService.billable_subscriptions\`
(which had the same >16 KB pin-storm signature).
\`\`\`ruby
ApplicationRecord.connected_to(role: :direct) do
RecurringTransactionRule.find_by_sql([sql, {today:}])
end
\`\`\`
No behaviour change for the app: \`:direct\` and \`:writing\` roles
point at the same physical DB in envs with a proxy; in envs without a
proxy they're the same URL. Only the connection routing differs.
## Test plan
- [x] Existing specs pass (no logic change, just routing wrap).
- [ ] After deploy on lago-prod-us-1: run \`SELECT
pg_stat_statements_reset();\` on \`lago-production\`, wait 10 min
covering at least one clock-scheduler firing of this service, then query
for \`length(query) > 16000\` — expect the \`WITH
pending_recurring_rules AS ...\` entry to be absent (or present but
never pinning, since it now bypasses the proxy).
- [ ] \`aws logs filter-log-events --log-group-name
/aws/rds/proxy/lago-production-rails-proxy --filter-pattern "pinned"\` —
no new pin events referencing the timestamps this service fires at. M
Maxime Vidori committed
cc12eb85f0cc3a9fa6727f2c88183d2c0b005648
Parent: 905cccd
Committed by GitHub <noreply@github.com>
on 8/3/2026, 7:50:38 AM