DEPLOY.CONTEXT.SETTING — The settings this migration is about to run under, read instead of assumed
- Category: safety
- Severity: derived — the check picks one per finding, from
infotohigh; see Why there is no single severity - Level: 0
- Downtime class: none — a deploy check reports on the state of a target, not on a DDL operation. The findings it raises carry their own, in the tables below.
- Stability: stable
- Suites: deploy
- Applies to: PostgreSQL and MySQL — two separate implementations behind one id, judging different settings
What it reports
sqlens:predeploy runs immediately before migrate --force, so when this check runs a DDL
statement is pending by construction. It reads the settings the server is configured with and
reports the ones that make that pending statement dangerous.
The timing is the whole rule. Unbounded lock_timeout is an ordinary, defensible server setting —
that is why the lint suite does not report it. What makes it a finding here is when here is: the
same value is fine on Tuesday and is an outage at the moment a migration runs.
The findings arrive in one fixed order, the order of the lists below, so two runs against one server produce one sequence rather than a diff.
It reads the SERVER's value, never the session's
SQLens bounds its own session on purpose: lock_timeout and statement_timeout are set by the
session defense before any read happens. So SHOW lock_timeout inside a SQLens run answers with
SQLens' own value, and a check that believed it would report this package's hygiene back to you
as your production configuration.
Every judgment therefore reads the server's value: reset_val on PostgreSQL — what a fresh session
gets — and the GLOBAL row on MySQL. The MySQL side matters for the same reason from a different
direction: Laravel sets sql_mode and the time zone per connection, so the value in effect for this
connection routinely differs from the server's.
PostgreSQL — four settings out of pg_settings
| Finding | Reported when | Severity | Downtime class |
|---|---|---|---|
DEPLOY.CONTEXT.SETTING.LOCK_TIMEOUT_UNBOUNDED | lock_timeout is 0 | high | blocking |
DEPLOY.CONTEXT.SETTING.IDLE_IN_TRANSACTION_UNBOUNDED | idle_in_transaction_session_timeout is 0 | medium | blocking |
DEPLOY.CONTEXT.SETTING.STATEMENT_TIMEOUT_UNBOUNDED | statement_timeout is 0 | low | online |
DEPLOY.CONTEXT.SETTING.MAX_WAL_SIZE_AT_DEFAULT | max_wal_size reads as a plain number of megabytes at or below 1024 | low | online |
lock_timeout = 0 — a DDL statement blocked behind a long-running transaction waits
indefinitely, and because ALTER TABLE queues an ACCESS EXCLUSIVE request, every read arriving
behind it queues too. The table stops answering while nothing looks broken.
idle_in_transaction_session_timeout = 0 — a session that opened a transaction and went away
holds its locks forever, and that is the single most common thing a migration blocks behind.
statement_timeout = 0 — reported rather than judged. It means a migration statement that turns
out to be far more expensive than expected has no upper bound of its own, so the deploy ends when it
ends.
max_wal_size at the shipped 1024 MB — a table rewrite generates far more WAL than that, which
forces checkpoints throughout: the rewrite takes longer and the I/O spike lands on everything else.
MySQL — four variables out of performance_schema, plus one about the reading itself
| Finding | Reported when | Severity | Downtime class |
|---|---|---|---|
DEPLOY.CONTEXT.SETTING.LOCK_WAIT_TIMEOUT_UNBOUNDED | lock_wait_timeout reads as a plain number of seconds above 3600 | high | blocking |
DEPLOY.CONTEXT.SETTING.FOREIGN_KEY_CHECKS_OFF | foreign_key_checks is neither ON nor 1 | high | online |
DEPLOY.CONTEXT.SETTING.SQL_MODE_NOT_STRICT | sql_mode carries neither STRICT_TRANS_TABLES nor STRICT_ALL_TABLES | high | online |
DEPLOY.CONTEXT.SETTING.ONLINE_ALTER_LOG_AT_DEFAULT | innodb_online_alter_log_max_size reads as a plain number of bytes at or below 134217728 (128 MiB) | medium | online |
DEPLOY.CONTEXT.SETTING.PROVENANCE_UNAVAILABLE | the variables came from SHOW GLOBAL VARIABLES rather than performance_schema | info | online |
lock_wait_timeout above an hour — MySQL ships 31536000, one year, which at deploy time is
indistinguishable from waiting forever: a DDL blocked on a metadata lock sits there, and every
statement that needs that table sits behind it. One hour is a declared line, not a measured one:
past it the deploy has already failed in every way that matters to whoever is watching it.
foreign_key_checks = OFF — a migration that adds a foreign key is accepted and validates
nothing. The constraint exists in the schema and the data behind it was never checked; the failure
surfaces later, as rows that violate a constraint the database believes it is enforcing.
sql_mode without strict mode — a column narrowed by this migration TRUNCATES the values that no
longer fit and reports a warning rather than an error. The migration succeeds, the deploy goes green,
and the data is gone.
innodb_online_alter_log_max_size at the shipped 128 MiB — an online ALTER buffers the DML that
arrives while it runs; when the buffer fills, the ALTER fails — after doing most of its work, and
under exactly the write load that made it fill.
PROVENANCE_UNAVAILABLE — performance_schema is off or not granted to this role, so the reading
fell back to SHOW GLOBAL VARIABLES. The values above are correct; what is missing is where each
came from. A value that looks deliberate may simply be the compiled-in default nobody ever set, and
this report cannot tell you which. It is a finding rather than an undetermined verdict on purpose:
undetermined would say the timeouts are unknown, when they were read perfectly well.
Why there is no single severity
The check picks a severity per finding, because the damage it is describing differs by more than a degree.
High is for the two shapes that end badly on their own: a wait nothing will end while an ACCESS EXCLUSIVE or metadata lock queues everything behind it, and a migration that is accepted while silently not doing what it says — an unvalidated foreign key, a truncating column narrow.
Medium and low descend from there to conditions that make a bad day worse rather than making one:
a missing bound on an idle transaction, a statement with no ceiling of its own, a default that turns
a rewrite into a slower rewrite. The one medium that does not fit that sentence is
ONLINE_ALTER_LOG_AT_DEFAULT, and it is medium rather than high because it is the default being
reported: the ALTER fails only if the write traffic during it fills the buffer, and this check has
not seen that traffic.
Info is the one finding that is about the reading rather than the server.
Both …_AT_DEFAULT findings additionally carry heuristic confidence; everything else this check
reports is deterministic.
What to do about it
The two timeout zeros on PostgreSQL are a decision, then one line. The check judges zero against non-zero and nothing else, so any non-zero value clears the finding — which is deliberately not a recommendation of a number. Decide how long a DDL statement may wait for its lock, and how long a session may sit idle inside a transaction, then set them. SQLens will not set them for you: it reads, and never sets a server-level value, not even for the duration of its own run.
statement_timeout = 0 is reported, not judged. The decision is whether the deploy should carry
an upper bound of its own. Nothing here says it must.
foreign_key_checks and sql_mode are the two to fix rather than weigh — in two different
places, which is the part that is easy to get wrong. sql_mode is a startup option;
foreign_key_checks is not one at all.
# my.cnf — a column narrowed by a migration should fail rather than truncate.
sql_mode = "STRICT_TRANS_TABLES"
-- foreign_key_checks has no startup option, so it CANNOT go in my.cnf: mysqld refuses to boot with
-- `unknown variable 'foreign-key-checks=ON'`. A global OFF is installed at run time, and run time
-- is where it goes back — a migration that adds a foreign key should validate it.
SET GLOBAL foreign_key_checks = ON;
Watch for the split here: Laravel already sets sql_mode per connection from
database.connections.*.strict, so your session can be strict while the server is not. This check
reads the server, which is what every other client gets.
lock_wait_timeout needs a value no higher than the hour this check draws its line at (3600
seconds — exactly 3600 clears the finding, only a larger value raises it). The line is declared so
it is arguable; the year MySQL ships is not.
The two …_AT_DEFAULT findings are the ones that genuinely say "decide, then act". Neither has
measured anything of yours: max_wal_size is judged against PostgreSQL's compiled-in default rather
than against the WAL your rewrite will produce, and innodb_online_alter_log_max_size against
MySQL's, rather than against the write traffic that will arrive during the ALTER. The question they
put to you is whether this deploy rewrites a large table or runs an online ALTER under load. If it
does not, the default is fine and the answer is a decision rather than a change.
PROVENANCE_UNAVAILABLE is fixed where the reading was blocked, by turning performance_schema
on or granting this role access to it. Nothing about the values changes either way.
When the check cannot answer
A check that cannot look reports undetermined with a named reason. It never reports a pass.
- The whole reading failed. The verdict names the reason and says what follows from it — the timeouts this migration will run under are unknown on PostgreSQL, the conditions it will run under on MySQL — and in both cases that is not the same as them being fine.
- One setting could not be judged. The verdict is
setting_unreadable:followed by one clause per setting. On PostgreSQL those clauses keep three states apart, because they send you to three different places: the row was withheld from the reading role (present, value masked), the name could not be seen by the reading role at all (a privilege, and the value may well be unsafe), or the name is not in this server's settings at all. Only some of those are fixable with a GRANT —pg_settingsfilters by privilege silently, with no error and nothing in the result to say a row was missing. On MySQL there is no masked-value state; a missing name means the server does not have that variable. - Undetermined does not throw away what was established. The findings that were judged still travel with the undetermined verdict, because a run that saw three of four problems reporting none of them would be the worse answer. What the verdict says is that the check cannot claim the settings are fine — a different sentence from "there is nothing here".
Undetermined blocks the deploy. See
pre-deploy permissions for the
privileges each reading needs and for --allow-undetermined, the one door out.
What it does not claim
- It never changes what it reads. No
SET GLOBALon MySQL, no server-level value on PostgreSQL, not even "just for this run". The session defense applies to SQLens' own connection and nothing else. - It reads the RUNNING value, never a configuration file. A line edited on disk and not yet
reloaded is invisible to this check, so a fix applied that way clears the finding only after the
server has picked it up. On PostgreSQL
reset_valis what a fresh session for this role and this database gets, so anALTER ROLE … SETorALTER DATABASE … SETcan make it differ from the file as well. - Nothing here says a blocker exists.
idle_in_transaction_session_timeoutandlock_wait_timeoutsay only that if one appears, nothing will end the wait. What is actually holding a lock right now is a separate finding —DEPLOY.PREFLIGHT.LOCK_BLOCKERandDEPLOY.PREFLIGHT.METADATA_LOCK_BLOCKER. - The two default findings are the default being reported, not a computed need. How much WAL a rewrite produces depends on the table, and how much DML arrives during an online ALTER depends on the traffic. This check has been told neither.
- These are the only settings it judges. Four names per engine, plus MySQL's provenance finding. A setting outside that list is not read for this verdict, so a quiet result here is not a statement about the server's configuration as a whole.
- A value it cannot parse as a plain number produces no finding.
max_wal_size,lock_wait_timeoutandinnodb_online_alter_log_max_sizeare compared numerically, and anything else is left unjudged rather than guessed at. - On MySQL, a missing source is not "nobody configured this". That is precisely what
PROVENANCE_UNAVAILABLEexists to prevent the report from implying.