Skip to content

Add per-table concurrent timeout overrides - #160

Merged
mathyoudawson merged 3 commits into
masterfrom
add-table-specific-timeout-overrides
Aug 20, 2026
Merged

Add per-table concurrent timeout overrides#160
mathyoudawson merged 3 commits into
masterfrom
add-table-specific-timeout-overrides

Conversation

@mathyoudawson

Copy link
Copy Markdown
Contributor

Summary

  • Adds an optional table_overrides config hash to register_database, letting apps override concurrent_lock_timeout/concurrent_statement_timeout for specific tables (e.g. a huge payments table needing a longer statement timeout than the database-wide default).
  • Falls back to the database-level value for any table (or key) not listed. Per-migration set_lock_timeout/set_statement_timeout still takes precedence over both.
  • Fully backward compatible: table_overrides defaults to {}, no behaviour changes for existing configs.
Nandi.configure do |config|
  config.register_database(:primary,
    concurrent_statement_timeout: 600_000,   # 10 min database-wide default
    table_overrides: {
      payments: { concurrent_statement_timeout: 1_800_000 }, # 30 min for payments
    })
end

Bumps version to 3.2.0.

Test plan

  • bundle exec rspec — 447 examples, 0 failures
  • bundle exec rubocop — 0 offenses
  • New specs added for MultiDatabase::Database, Config, and Migration covering: override present/absent, partial-key overrides, string/symbol table names, per-migration override precedence, and multi-database database_name + table resolution together

🤖 Generated with Claude Code

…errides

Lets apps configure a table_overrides hash on register_database so a
specific table (e.g. a huge payments table) can have a longer
concurrent statement/lock timeout than the database-wide default,
without loosening it for every table. Falls back to the database-level
value for any table (or key) not listed; per-migration
set_lock_timeout/set_statement_timeout still takes precedence.

Bumps version to 3.2.0.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@mathyoudawson
mathyoudawson marked this pull request as ready for review August 14, 2026 14:30
table and mixins were each concatenating up_instructions + down_instructions
inline; pull that into a shared private helper instead.

@yermold yermold left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Nice! Left a couple questions.

Comment on lines +137 to +139
def normalize_table_overrides(table_overrides)
(table_overrides || {}).transform_keys(&:to_sym)
end

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

config doesn't allow non-sym keys for other params, why would we want to be so forgiving in this case?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

IMO the table_overrides config is a special because the keys are table names. Nandi already handles table names as strings or symbols in DB instructions (e.g. add_index :table_name and add_index "table_name").

It's conceivable that someone might assume that this config behaves the same way. By silently ignoring strings we would use the default timeout.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Okay, then we could also ensure that string table-name inputs are supported by using @table_overrides = (config[:table_overrides] || {}).with_indifferent_access, so we don't silently ignore if someone accesses config using a string table name.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

This would cause the inner hash to be tolerant of string keys too. So table_overrides: { payments: { "concurrent_statement_timeout" => 600_000 } } would be supported.

In this case I just want to handle table names as strings rather than changing the behaviour of how inner options can be configured.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

with_indifferent_access is not deep by default

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

It is deep by default. I tested this in a rails console

{ payments: { "inner_key" => "ok" } }.with_indifferent_access[:payments][:inner_key]
=> "ok"

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

That's surprising. I expected it to operate on a single level like the other hash helpers. Good to know :)

end
end

context "when a table_overrides entry exists for the requested table" do

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Shouldn't these specs live in the main migration spec file?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

These tests are testing different things in different environments.

The migration specs are effectively an integration test. The specs that were added checks that the table overrides work with the rest of the migration logic.

The multi_database specs are testing the actual override timeout calculation logic in isolation.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Ok, I was confused that there’s logic and specs specifically for the “multi-database” case, but nothing for the “single-database” use case. Then I realised that the multi-database code superseded the single-database setup, so everything in Nandi is now multi-database by default.

@yermold yermold left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

LGTM

@mathyoudawson
mathyoudawson merged commit a423b98 into master Aug 20, 2026
10 checks passed
@mathyoudawson
mathyoudawson deleted the add-table-specific-timeout-overrides branch August 20, 2026 08:29
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

Successfully merging this pull request may close these issues.

2 participants