These are Liquibase-specific escape hatches, and interviewers expect you to know why they exist and why they’re dangerous.
runAlways
runAlways=truemeans the changeset executes every time Liquibase runs, even if it was already applied.
Example
<changeSet id="refresh_view" author="stanley" runAlways="true">
<sql>
REFRESH MATERIALIZED VIEW user_stats;
</sql>
</changeSet>
Key properties
- Ignores execution history
- Runs on every startup / deploy
- Checksum is irrelevant
- History row still exists, but execution is repeated
Typical use cases (valid)
- Refreshing views
- Recreating derived data
- Syncing reference data
- Granting permissions
Why it’s risky
- Can be expensive
- Can lock tables
- Easy to hide performance problems
- Never use for schema changes
runOnChange
runOnChange=truemeans the changeset runs again only if its contents change.
Example
<changeSet id="recreate_function" author="stanley" runOnChange="true">
<sql>
CREATE OR REPLACE FUNCTION normalize_name(text) RETURNS text AS $$
BEGIN
RETURN lower(trim($1));
END;
$$ LANGUAGE plpgsql;
</sql>
</changeSet>
Key properties
- Liquibase stores checksum
- If file content changes → checksum changes → re-run
- If unchanged → skipped
- Still deterministic
Typical use cases (valid)
- Stored procedures
- Functions
- Triggers
- Views (sometimes)
Why it’s risky
- Encourages editing applied migrations
- Can surprise reviewers (“why did this run again?”)
- Dangerous if mixed with data changes
Side-by-side comparison
| Aspect | runAlways | runOnChange |
|---|---|---|
| Runs every time | ✅ Yes | ❌ No |
| Runs on file change | ❌ No | ✅ Yes |
| Uses checksum | ❌ Ignored | ✅ Required |
| Deterministic | ❌ No | ⚠️ Semi |
| Safe for schema | ❌ No | ❌ No |
| Typical target | Derived data | Code-like DB objects |
What NOT to do (red flags 🚨)
❌ Use either for:
ALTER TABLEDROP COLUMN- irreversible data changes
❌ Use to “fix” a bad migration instead of adding a new one
Interview-ready explanation (2 sentences)
runAlwaysforces a changeset to execute on every Liquibase run regardless of history, whilerunOnChangere-executes a changeset only when its contents change and the checksum differs. Both are intended for derived or code-like database objects, not for schema evolution, and should be avoided for production schema changes.
Senior rule of thumb (remember this)
If it changes schema or business data, it should run once and never again.