FAQ / Troubleshooting
Practical answers to common questions and failure modes when working with mat_views (definitions â services â jobs â rake tasks), the demo app, and PostgreSQL materialized views.
Quick diagnostics (copy-paste)
Is my MV there? Whatâs its schema?
SELECT schemaname, matviewname FROM pg_matviews ORDER BY 1,2;
What unique indexes exist on an MV?
SELECT i.relname AS index_name, idx.indisunique, a.attname AS column
FROM pg_class t
JOIN pg_index idx ON idx.indrelid = t.oid
JOIN pg_class i ON i.oid = idx.indexrelid
LEFT JOIN pg_attribute a ON a.attrelid = t.oid AND a.attnum = ANY(idx.indkey)
JOIN pg_namespace n ON n.oid = t.relnamespace
WHERE n.nspname = 'public' AND t.relname = 'mv_user_activity';
Whoâs holding a lock on my MV?
SELECT pid, usename, state, query
FROM pg_stat_activity
WHERE query ILIKE '%mv_user_activity%'
ORDER BY query_start;
Latest run status per view (create):
SELECT d.name, r.status, r.duration_ms, r.finished_at
FROM mat_view_definitions d
LEFT JOIN LATERAL (
SELECT * FROM mat_view_runs r
WHERE r.mat_view_definition_id = d.id
AND r.operation = 'create'
ORDER BY r.created_at DESC
LIMIT 1
) r ON true
ORDER BY d.name;
Latest run status per view (refresh):
SELECT d.name, r.status, r.duration_ms, r.finished_at
FROM mat_view_definitions d
LEFT JOIN LATERAL (
SELECT * FROM mat_view_runs r
WHERE r.mat_view_definition_id = d.id
AND r.operation = 'refresh'
ORDER BY r.created_at DESC
LIMIT 1
) r ON true
ORDER BY d.name;
Latest run status per view (drop):
SELECT d.name, r.status, r.duration_ms, r.finished_at
FROM mat_view_definitions d
LEFT JOIN LATERAL (
SELECT * FROM mat_view_runs r
WHERE r.mat_view_definition_id = d.id
AND r.operation = 'drop'
ORDER BY r.created_at DESC
LIMIT 1
) r ON true
ORDER BY d.name;
Latest run status per view (all):
SELECT d.name, r.status, r.duration_ms, r.finished_at
FROM mat_view_definitions d
LEFT JOIN LATERAL (
SELECT * FROM mat_view_runs r
WHERE r.mat_view_definition_id = d.id
ORDER BY r.created_at DESC
LIMIT 1
) r ON true
ORDER BY d.name;
General
âNothing happens when I run a rake taskâ
- Ensure Rails environment is loaded (
=> :environmentis present). - Tasks log via
Rails.logger(notputs). In development:- Ensure the logger outputs to STDOUT and the log level isnât too high.
- If the task enqueues jobs, make sure your queue backend is running (or use
config.active_job.queue_adapter = :inlinefor immediate execution in dev). - Add
--yesorYES=1to bypass confirmation prompts.
âAdapter guesses the wrong backend?â
- It does not guess. The adapter calls the unified API:
MatViews::Jobs::Adapter.enqueue(job, queue:, args:)It relies on your appâs configured backend (ActiveJob, Sidekiq, Resque). Configure your backend per your appâs needs.
Definitions (schema & SQL)
âInvalid view name formatâ
- Definitions must use simple identifiers (no dots):
mv_user_activityâ ,public.mv_user_activityâ - Schema is resolved via
search_pathand quoted safely by services.
âView exists in DB but thereâs no MatViewDefinitionâ
- Tasks intentionally raise a guardrail error if you pass
public.my_mvand thereâs no matchingMatViewDefinition. - Fix: create the definition record (preferred) or drop the orphan MV manually if itâs truly obsolete.
Create
âCreate failed with SQL errorâ
- Check your definitionâs
sql. Run it as a plainSELECTfirst. - Verify permissions (e.g., on referenced tables).
- For large outputs, consider
WITH NO DATAâ index â refresh (see Best Practices).
âCreate idempotentlyâ
- Use the job/task flow; pass
forceonly when you want to rebuild. - Pattern:
CREATE ... WITH NO DATA, then build indexes concurrently, then refresh concurrently.
Refresh
âcannot refresh materialized view concurrently without a unique indexâ
- Concurrent refresh requires a unique index that covers all rows of the MV result.
-
Create it first (often on
id/user_id):CREATE UNIQUE INDEX CONCURRENTLY IF NOT EXISTS idx_mv_user_activity_user_id ON "public"."mv_user_activity"(user_id);
âConcurrent refresh fails in a transactionâ
REFRESH MATERIALIZED VIEW CONCURRENTLYcannot run inside a transaction block.- Ensure your orchestration does not wrap it with
BEGIN ⌠COMMIT.
âRegular refresh locks readersâ
- Thatâs expected for regular refresh. Use concurrent (requires unique index) or the swap strategy to minimize read downtime.
âRefresh fails due to locksâ
- Check
pg_stat_activityto find long transactions or sessions referencing the MV. - Consider retry logic, stagger refresh schedules, or switch to swap if appropriate.
Swap refresh
âIndexes/privileges disappeared after swapâ
- The new temp MV must receive the same indexes and grants before you rename it into place.
- Ensure your
SwapRefreshimplementation recreates all required indexes and permissions.
Delete
âDependencies exist. Use cascade: trueâ
- Another object (e.g., a view) depends on the MV. Drop dependents first or pass
cascade: true. - Rake task:
mat_views:delete_by_name[mv_name,true,--yes]
âDelete should be idempotentâ
- Use
if_exists: true(default in the service). Result is:skippedwhen the MV isnât present.
Rake tasks
âHow do I skip confirmation?â
-
Pass
--yesas the last arg or setYES=1:bin/rake 'mat_views:create_all[,--yes]' YES=1 bin/rake 'mat_views:refresh_all[]'
âWhich arguments do tasks accept?â
- Create:
[view_name, force, --yes]/[mat_view_definition_id, force, --yes]/[force, --yes] -
Refresh: [view_name, row_count_strategy, --yes](estimatedexact blank) -
Delete: [view_name, cascade, --yes](truefalse)
âName vs schema-qualified name?â
- You may pass
public.mv_name. If a physical MV exists but no definition, tasks raise to avoid acting on unknown objects. - Prefer passing definition names (simple identifier).
Jobs & Adapter
âWhich queue do jobs use?â
MatViews.configuration.job_queue(default:default). Override in your initializer.
âDo jobs record results anywhere?â
- Yes. Each job writes a run row:
- Create â
mat_view_runswithoperation: :create - Refresh â
mat_view_runswithoperation: :refresh - Delete â
mat_view_runswithoperation: :drop
- Create â
- They include
status,started_at,finished_at,duration_ms,error, andmeta.
âHow do I enqueue?â
MatViews::Jobs::Adapter.enqueue(
MatViews::RefreshViewJob,
queue: MatViews.configuration.job_queue,
args: [definition.id, :estimated] # or :exact
)
- The adapter does not detect your backend; it assumes itâs already configured (ActiveJob/Sidekiq/Resque).
Validation & Benchmarking
âValidator says: no MVs foundâ
-
Create views first (bootstrap demo or your own definitions):
bin/rake 'mat_views:bootstrap_demo[1,--yes]' # or bin/rake 'mat_views:create_all[,--yes]' && bin/rake 'mat_views:refresh_all[,--yes]'
âFound MVs in DB but no definitionsâ
- The validator needs definitions to get the baseline SQL.
- Ensure
MatViews::MatViewDefinitionexists for each MV name.
âRow counts differ (baseline vs MV)â
- Your MV definition may have drifted from the baseline SQL or missed filters/joins.
- Recreate/swap the MV with the current definition.
- For stricter checks, extend the validator to compare aggregates or sampled rows.
âHuge variance in timingsâ
- Run more iterations (e.g., 100â300).
- Ensure a quiet DB; watch
pg_stat_activity. - Consider a warmup run if you want hot-cache numbers.
Permissions
âPermission denied on MVâ
-
Grant read access explicitly:
GRANT SELECT ON "public"."mv_user_activity" TO app_user; -
Also verify privileges on underlying tables if you run baseline SQL in validation.
Search path & quoting
âMy view is in a non-public schemaâ
- The services resolve schema from
search_path, including quoted tokens and$user. If the resolved schema doesnât exist, they fallback topublic. - For explicit control, keep
search_pathpredictable per environment.
âWhy do you quote identifiers?â
- We quote
schemaandrelationwithquote_table_name/ identifier rules to avoid conflicts with mixed case or reserved words.
Testing & CI
âHow do I silence logs in specs?â
-
In RSpec, mock
Rails.loggerwith a spy:let(:fake_logger) { instance_spy(Logger, info: ->(*){}, warn: ->(*){}, error: ->(*){}, debug: ->(*){}) } allow(Rails).to receive(:logger).and_return(fake_logger)
âHow do I test enqueue calls?â
-
Set up a spy:
allow(MatViews::Jobs::Adapter).to receive(:enqueue) # ... expect(MatViews::Jobs::Adapter).to have_received(:enqueue) .with(MatViews::RefreshViewJob, queue: anything, args: [defn.id, :estimated])
Common error â fix cheatsheet
| Error message (snippet) | Likely cause | Fix |
|---|---|---|
Invalid view name format | Definition name has a dot or invalid chars | Use simple identifier (e.g., mv_users) |
cannot refresh ... concurrently without a unique index | Missing unique index | Create unique index; refresh again |
... inside a transaction block | Running concurrent refresh in a transaction | Run outside BEGIN ... COMMIT |
dependencies exist. Use cascade: true | Dropping MV with dependents | Drop dependents or cascade: true |
No MatViews::MatViewDefinition found | Name mismatch / missing definition | Create the definition or rename MV |
| âNo MVs foundâ in validator | Nothing in pg_matviews | Create MVs (bootstrap or create tasks) |
| Row counts mismatch | MV SQL drifted | Recreate/swap MV; compare SQL carefully |
| Jobs donât run | Backend not running | Start your queue engine or use :inline in dev |
Still stuck?
- Inspect the run rows (create/refresh/delete) for error strings and timing.
- Look at
meta[:sql]in responses or logs for the exact SQL executed. - Enable more verbose DB logs (Postgres
log_statement,log_duration) in a dev/staging environment. - Raise an issue on the projectâs GitHub with details about your setup, Postgres version, and any relevant logs.
- We offer Professional support for complex issues or custom integrations. Contact us at sales@codevedas.com for inquiries.