Skip to content

add: per request statement timeout using prefer header#4584

Draft
taimoorzaeem wants to merge 1 commit intoPostgREST:mainfrom
taimoorzaeem:add/per-request-timeout
Draft

add: per request statement timeout using prefer header#4584
taimoorzaeem wants to merge 1 commit intoPostgREST:mainfrom
taimoorzaeem:add/per-request-timeout

Conversation

@taimoorzaeem
Copy link
Collaborator

@taimoorzaeem taimoorzaeem commented Dec 29, 2025

Adds a feature to set statement_timeout using Prefer: timeout header. This also introduces a PGRST129 error which is returned when the timeout preferred exceeds the per-role timeout, which prevents misuse of this feature.

Closes #4381.

  • Implementation
  • Tests
  • Docs

@taimoorzaeem taimoorzaeem force-pushed the add/per-request-timeout branch from d074eb9 to 2c5589f Compare December 29, 2025 16:37
@taimoorzaeem taimoorzaeem marked this pull request as draft December 29, 2025 16:40
@taimoorzaeem taimoorzaeem force-pushed the add/per-request-timeout branch 3 times, most recently from 5f1d601 to 6963e9a Compare January 1, 2026 07:48
@taimoorzaeem taimoorzaeem changed the title [WIP] add: per request statement timeout using prefer header add: per request statement timeout using prefer header Jan 1, 2026
@taimoorzaeem taimoorzaeem marked this pull request as ready for review January 1, 2026 07:48
@steve-chavez
Copy link
Member

We should also fail in case a value exceeds the per role timeout. Otherwise it's abusable.

This failure should happen at the plan level, before hitting the db. I think it will need a new error code.

@taimoorzaeem taimoorzaeem marked this pull request as draft January 2, 2026 08:23
@taimoorzaeem taimoorzaeem force-pushed the add/per-request-timeout branch 5 times, most recently from a82c07f to 0d6370e Compare January 7, 2026 07:13
@taimoorzaeem taimoorzaeem marked this pull request as ready for review January 7, 2026 07:15
@taimoorzaeem taimoorzaeem added the enhancement a feature, ready for implementation label Jan 7, 2026
-- It is safe to use fromJust because the string being read is guaranteed
-- to be number after striping the unit.
stripUnitAndGetInt unit val = fromJust $ BS.stripSuffix unit val >>= readMaybe
rtValueToSeconds :: ByteString -> Int
Copy link
Member

Choose a reason for hiding this comment

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

I'm confused by all of this logic, why do we need to parse a time unit?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

We should also fail in case a value exceeds the per role timeout. Otherwise it's abusable

I'm confused by all of this logic, why do we need to parse a time unit?

That's because the statement_timeout value of the role could be like 1min or 100ms depending upon the role. So, we need to throw an error depending on whether the timeout exceeds role setting or not and for that, we must parse the value, I think.

Copy link
Member

Choose a reason for hiding this comment

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

Can we do the conversion in postgres to avoid the parsing? For example with:

select extract(epoch from current_setting('statement_timeout', false)::interval)::int;

That should give you the number of seconds regardless of the input format.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

That's great.

However, if we do this conversion at the time of querying the role settings, that would change the original role settings, which we explicitly add to pre-query (BTW, I'm not sure why we do this, wouldn't postgres already know the settings?) here:

roleSettingsSql = setConfigWithDynamicName <$> HM.toList (fromMaybe mempty $ HM.lookup authRole configRoleSettings)

So, we would then have to keep the two sets of role settings, one with the conversion applied on and the original ones without the conversion.

Copy link
Member

Choose a reason for hiding this comment

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

we explicitly add to pre-query (BTW, I'm not sure why we do this, wouldn't postgres already know the settings?)

The settings set via ALTER ROLE ... only apply when logging in with that role. PostgreSQL does not apply them when doing a SET ROLE. That means, these role-specific settings are only set when we do it manually.

Copy link
Member

@steve-chavez steve-chavez Jan 18, 2026

Choose a reason for hiding this comment

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

@taimoorzaeem Hm, so we can't avoid this logic with the above snippet?

Copy link
Collaborator Author

@taimoorzaeem taimoorzaeem Jan 18, 2026

Choose a reason for hiding this comment

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

Yes. If we try to avoid this logic using the above snippet, we would end up with maintaining more state (keep extra pre-computed values, even when they are never used) so I think we should avoid it.

Copy link
Member

Choose a reason for hiding this comment

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

Sorry for late reply here.

If we try to avoid this logic using the above snippet, we would end up with maintaining more state (keep extra pre-computed values, even when they are never used) so I think we should avoid it.

IMO we should cache the pg transformed value.

  • Having to repeat this transformation logic for each request at the Plan level would be inefficient.
  • It's not much more memory considering we only take child roles of the connection role:
    where member = current_user::regrole::oid
  • Also later on we could avoid this caching if Prefer: timeout is not used with Config flag for enabling/disabling preferences #2987.
  • There is more risk for errors with this Haskell logic.. say if pg changes format of the statement_timeout, very unlikely but overall it feels safer to have the transformation at the db level.

@taimoorzaeem taimoorzaeem marked this pull request as draft January 12, 2026 07:27
@taimoorzaeem taimoorzaeem force-pushed the add/per-request-timeout branch from 0d6370e to 6481e8e Compare January 13, 2026 05:40
@taimoorzaeem taimoorzaeem marked this pull request as ready for review January 13, 2026 05:41
@taimoorzaeem taimoorzaeem force-pushed the add/per-request-timeout branch from 6481e8e to a37d497 Compare January 15, 2026 14:56
@taimoorzaeem taimoorzaeem force-pushed the add/per-request-timeout branch 2 times, most recently from 1998a61 to cef3feb Compare January 25, 2026 07:09
@taimoorzaeem taimoorzaeem force-pushed the add/per-request-timeout branch 2 times, most recently from cd53ca3 to 356cecf Compare January 28, 2026 07:32
@taimoorzaeem taimoorzaeem marked this pull request as draft January 29, 2026 12:03
Adds a feature to set `statement_timeout` using `Prefer: timeout`
header. This also introduces a `PGRST129` error which is returned
when the timeout preferred exceeds the per-role timeout, which
prevents misuse of this feature.

Signed-off-by: Taimoor Zaeem <taimoorzaeem@gmail.com>
@taimoorzaeem taimoorzaeem force-pushed the add/per-request-timeout branch from 356cecf to a1f19e7 Compare February 3, 2026 11:30
Comment on lines +121 to +124
-- Cached statement timeout settings converted to number of seconds. They
-- are never applied, only used to check max allowed timeout for a role
-- when "Prefer: timeout=" header is used.
, configRoleTimeoutSettings :: RoleTimeoutSettings
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

IMO we should cache the pg transformed value

@steve-chavez How about an internal config to store the transformed values?

Copy link
Member

Choose a reason for hiding this comment

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

Yes, this looks much better 👍

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement a feature, ready for implementation

Development

Successfully merging this pull request may close these issues.

Per-request timeout

3 participants