New to Kestra?
Use blueprints to kickstart your first workflows.
Group system:indexes by keyspace, keys and condition to find redundant online Couchbase GSI indexes, then drop the surplus behind a dry run and an approval.
Two online GSI indexes covering the same keys on the same keyspace do the same work twice. Every
mutation updates both, both hold their own copy in index RAM and the query service picks one of
them, so the second is paid for on every write and never read. Couchbase reports no error for it.
It happens after a rename that left the old index behind, a migration that was re-run or two
people adding the same index. This blueprint groups system:indexes, keeps one index per group,
drops the surplus behind a dry-run gate and a human approval, then re-reads system:indexes and
fails the run if any group still has more than one member.
This blueprint was created by zkasuran.
Dropping an index is not reversible, so the rules are in the query and the task graph rather than only in this text.
is_primary out. A separate
query re-reads system:indexes for primary indexes whose name appears in the plan. One hit
fails the run before any drop.min_indexes_retained makes the floor explicit: the run fails if the online index count
minus the planned drops would fall under it.state is online are grouped. An index that is still deferred or
building is a different problem and is left alone.condition is part of the group key. Two indexes on the same keys with different
WHERE clauses are partial indexes serving different queries, not duplicates. Dropping one of
them would delete a real optimisation, so they are never grouped together. partition and
using are in the group key for the same reason.dry_run defaults to true and auto_remediate to false, so a plain run
only reports. The DROP INDEX runs only when auto_remediate is true, dry_run is false and a
human answers the Pause with approved: true. An unanswered resume reads as not approved.concurrencyLimit: 1, so index drops are serialised rather than racing
each other on the same keyspace.detect (io.kestra.plugin.couchbase.Query) groups system:indexes by keyspace, index_key, condition and partition, over online non-primary GSI indexes on the target keyspace. LETTING members = ARRAY_SORT(ARRAY_AGG(name)) plus HAVING ARRAY_LENGTH(members) > 1 leaves one row per duplicate group. members[0] is the index that is kept and members[1:] is the surplus, so the survivor is the same on every run.count_online counts every online index on the keyspace, primary included. That number feeds the floor check.plan (io.kestra.plugin.core.output.OutputValues) turns the group rows into the counts, the name lists and the drop targets the later tasks read.guard_primary asks Couchbase whether any planned name is a primary index. The answer comes from system:indexes rather than from the plan, so it is a real check and not a restatement.report (io.kestra.plugin.core.log.Log) logs the counts, the kept names, the surplus names and the full group rows.on_findings (io.kestra.plugin.core.flow.If) posts a Slack findings alert through io.kestra.plugin.slack.notifications.SlackIncomingWebhook whenever there is surplus. It fires on a dry run too.safety_guard fails the run with io.kestra.plugin.core.execution.Fail if a primary index is in the plan or if the keyspace would drop under min_indexes_retained. Nothing is dropped in that case.remediate runs only when there is surplus and auto_remediate is true and dry_run is false. approval_hold (io.kestra.plugin.core.flow.Pause) waits for a human. On approval drop_surplus (io.kestra.plugin.core.flow.Loop, concurrencyLimit: 1) issues one DROP INDEX <name> ON <keyspace> per surplus index.reverify runs the grouping query again. reverify_gate fails the run if any group still has more than one member, so a partial drop never passes as healthy. On a declined approval the flow logs it and leaves the indexes alone. The errors block posts a Slack alert on any failure.DROP INDEX remediation gated on dry run, an explicit flag and a human approval.system:indexes that fails the run when the drop did not land.WHERE clause is part of the index identity.Couchbase does not warn about a redundant index. It builds it, maintains it on every mutation and
keeps it resident. Finding the overlap needs a grouped read of system:indexes. Acting on it
needs a judgement call about which copy survives. Kestra runs the read on a schedule, keeps the
DROP INDEX behind an approval, re-reads the cluster to confirm the result and routes findings and
failures to Slack. Every run is an execution you can replay with the plan it used.
Verified on Kestra 2.0.5 against a real Couchbase cluster (couchbase/server:community) with the
data, index and query services enabled, confirmed before and after with direct N1QL queries
against system:indexes.
WHERE clauses were not in it.dry_run=true with auto_remediate=true: the findings alert posted, no DROP INDEX ran and system:indexes was unchanged afterward.dry_run=false with auto_remediate=true, approved: the surplus index was dropped, the re-read returned 0 groups and the keyspace kept the differently keyed index, both partial indexes and the primary index.min_indexes_retained above what the keyspace would keep, the run failed at safety_guard and nothing was dropped.Pause meant the drop left a duplicate group behind, the re-read saw it and the run failed instead of reporting success.detect and the errors block posted a Slack alert.Couchbase needs the query and index services enabled, otherwise system:indexes and DROP INDEX
both fail. The user needs privileges to read system:indexes and to drop indexes on the target
keyspace. Use _default for both scope and collection on a bucket that has no named scopes.
COUCHBASE_PASSWORD: password for the Couchbase user. It is the default for the password input.SLACK_WEBHOOK_URL: incoming webhook used by the findings alert and the error alert.connection_string: Couchbase connection string, for example couchbase://host1,host2.username: Couchbase user with query and index privileges.bucket: bucket holding the keyspace to inspect.scope: scope inside the bucket. Defaults to _default.collection: collection inside the scope. Defaults to _default.password: user password, defaulting to the COUCHBASE_PASSWORD secret.dry_run: report only when true. Defaults to true.auto_remediate: drop the surplus when true and dry_run is false. Defaults to false.min_indexes_retained: floor on the online index count after the drops. Defaults to 1.COUCHBASE_PASSWORD and SLACK_WEBHOOK_URL secrets in Kestra.dry_run true to see the groups, the kept name and the surplus names.dry_run false and auto_remediate true, then answer the Pause with approved: true.LETTING clause.num_replica to the group key when you run replicated indexes and want replica counts to differ.min_indexes_retained on a keyspace that must always keep a known number of indexes.