diff --git a/lib/gat/gatcaddyfile/digitalocean_filter.go b/lib/gat/gatcaddyfile/digitalocean_filter.go
new file mode 100644
index 0000000000000000000000000000000000000000..d0dc672a92fd57e5c1663b7495fd71cf9dc6c208
--- /dev/null
+++ b/lib/gat/gatcaddyfile/digitalocean_filter.go
@@ -0,0 +1,26 @@
+package gatcaddyfile
+
+import (
+	"github.com/caddyserver/caddy/v2"
+	"github.com/caddyserver/caddy/v2/caddyconfig"
+	"github.com/caddyserver/caddy/v2/caddyconfig/caddyfile"
+
+	"gfx.cafe/gfx/pggat/lib/gat/handlers/discovery/discoverers/digitalocean/filters/tag"
+	"gfx.cafe/gfx/pggat/lib/util/strutil"
+)
+
+func init() {
+	RegisterDirective(
+		DigitaloceanFilter,
+		"tag",
+		func(d *caddyfile.Dispenser, _ *[]caddyconfig.Warning) (caddy.Module, error) {
+			if !d.NextArg() {
+				return nil, d.ArgErr()
+			}
+
+			return &tag.Filter{
+				Tag: strutil.Matcher(d.Val()),
+			}, nil
+		},
+	)
+}
diff --git a/lib/gat/gatcaddyfile/discoverer.go b/lib/gat/gatcaddyfile/discoverer.go
index 61f220f921979913cdc81bb52f8ca9bb35b3214a..ec5b4c5826517e60071cc96075b60d5d21e7dde0 100644
--- a/lib/gat/gatcaddyfile/discoverer.go
+++ b/lib/gat/gatcaddyfile/discoverer.go
@@ -8,7 +8,6 @@ import (
 	"gfx.cafe/gfx/pggat/lib/gat/handlers/discovery/discoverers/digitalocean"
 	"gfx.cafe/gfx/pggat/lib/gat/handlers/discovery/discoverers/google_cloud_sql"
 	"gfx.cafe/gfx/pggat/lib/gat/handlers/discovery/discoverers/zalando_operator"
-	"gfx.cafe/gfx/pggat/lib/util/strutil"
 )
 
 func init() {
@@ -53,7 +52,16 @@ func init() {
 						return nil, d.ArgErr()
 					}
 
-					module.Filter = strutil.Matcher(d.Val())
+					var err error
+					module.Filter, err = UnmarshalDirectiveJSONModuleObject(
+						d,
+						DigitaloceanFilter,
+						"filter",
+						warnings,
+					)
+					if err != nil {
+						return nil, err
+					}
 				default:
 					return nil, d.ArgErr()
 				}
diff --git a/lib/gat/gatcaddyfile/unmarshallers.go b/lib/gat/gatcaddyfile/unmarshallers.go
index 8b8b63abc1765cbfa1b8ac415935e336be7c58e4..dff13ab03dabff03e7551e73e7f99222380bd194 100644
--- a/lib/gat/gatcaddyfile/unmarshallers.go
+++ b/lib/gat/gatcaddyfile/unmarshallers.go
@@ -11,13 +11,14 @@ import (
 )
 
 const (
-	Discoverer = "pggat.handlers.discovery.discoverers"
-	Handler    = "pggat.handlers"
-	Matcher    = "pggat.matchers"
-	Pool       = "pggat.handlers.pool.pools"
-	Pooler     = "pggat.handlers.pool.poolers"
-	SSLServer  = "pggat.ssl.servers"
-	SSLClient  = "pggat.ssl.clients"
+	Discoverer         = "pggat.handlers.discovery.discoverers"
+	DigitaloceanFilter = "pggat.handlers.discovery.discoverers.digitalocean.filters"
+	Handler            = "pggat.handlers"
+	Matcher            = "pggat.matchers"
+	Pool               = "pggat.handlers.pool.pools"
+	Pooler             = "pggat.handlers.pool.poolers"
+	SSLServer          = "pggat.ssl.servers"
+	SSLClient          = "pggat.ssl.clients"
 )
 
 var unmarshallers maps.TwoKey[string, string, Unmarshaller]
diff --git a/lib/gat/handlers/discovery/discoverers/digitalocean/config.go b/lib/gat/handlers/discovery/discoverers/digitalocean/config.go
index 32ff49272586a7e49fc11ccdc9f549f475a67b9a..1c232ddbaed48f5ace31d3b8136f234c32bc523f 100644
--- a/lib/gat/handlers/discovery/discoverers/digitalocean/config.go
+++ b/lib/gat/handlers/discovery/discoverers/digitalocean/config.go
@@ -1,10 +1,12 @@
 package digitalocean
 
-import "gfx.cafe/gfx/pggat/lib/util/strutil"
+import (
+	"encoding/json"
+)
 
 type Config struct {
 	APIKey  string `json:"api_key"`
 	Private bool   `json:"private,omitempty"`
 
-	Filter strutil.Matcher `json:"filter,omitempty"`
+	Filter json.RawMessage `json:"filter,omitempty" caddy:"namespace=pggat.handlers.discovery.discoverers.digitalocean.filters inline_key=filter"`
 }
diff --git a/lib/gat/handlers/discovery/discoverers/digitalocean/discoverer.go b/lib/gat/handlers/discovery/discoverers/digitalocean/discoverer.go
index 59942c33ec377cdbb0bfaa74191c235dc879c686..89e4e4f2ec03927dc6f351fcd753a22a5499d4ef 100644
--- a/lib/gat/handlers/discovery/discoverers/digitalocean/discoverer.go
+++ b/lib/gat/handlers/discovery/discoverers/digitalocean/discoverer.go
@@ -2,6 +2,7 @@ package digitalocean
 
 import (
 	"context"
+	"fmt"
 	"net"
 	"strconv"
 
@@ -18,6 +19,8 @@ func init() {
 type Discoverer struct {
 	Config
 
+	filter Filter
+
 	do *godo.Client
 }
 
@@ -31,17 +34,16 @@ func (T *Discoverer) CaddyModule() caddy.ModuleInfo {
 }
 
 func (T *Discoverer) Provision(ctx caddy.Context) error {
-	T.do = godo.NewFromToken(T.APIKey)
-	return nil
-}
-
-func (T *Discoverer) filter(tags []string) bool {
-	for _, tag := range tags {
-		if T.Filter.Matches(tag) {
-			return true
+	if T.Filter != nil {
+		val, err := ctx.LoadModule(T, "Filter")
+		if err != nil {
+			return fmt.Errorf("loading filter module: %v", err)
 		}
+		T.filter = val.(Filter)
 	}
-	return T.Filter.Matches("")
+
+	T.do = godo.NewFromToken(T.APIKey)
+	return nil
 }
 
 func (T *Discoverer) Clusters() ([]discovery.Cluster, error) {
@@ -57,7 +59,7 @@ func (T *Discoverer) Clusters() ([]discovery.Cluster, error) {
 		}
 
 		// filter by tags
-		if !T.filter(cluster.Tags) {
+		if T.filter != nil && !T.filter.Allow(cluster) {
 			continue
 		}
 
@@ -92,7 +94,7 @@ func (T *Discoverer) Clusters() ([]discovery.Cluster, error) {
 		c.Replicas = make(map[string]discovery.Node, len(replicas))
 		for _, replica := range replicas {
 			// filter by tags
-			if !T.filter(replica.Tags) {
+			if T.filter != nil && !T.filter.AllowReplica(replica) {
 				continue
 			}
 
diff --git a/lib/gat/handlers/discovery/discoverers/digitalocean/filter.go b/lib/gat/handlers/discovery/discoverers/digitalocean/filter.go
new file mode 100644
index 0000000000000000000000000000000000000000..77ef8272747ef71d1b3f54de0a57e5b4bc6ce4d6
--- /dev/null
+++ b/lib/gat/handlers/discovery/discoverers/digitalocean/filter.go
@@ -0,0 +1,8 @@
+package digitalocean
+
+import "github.com/digitalocean/godo"
+
+type Filter interface {
+	Allow(database godo.Database) bool
+	AllowReplica(database godo.DatabaseReplica) bool
+}
diff --git a/lib/gat/handlers/discovery/discoverers/digitalocean/filters/tag/filter.go b/lib/gat/handlers/discovery/discoverers/digitalocean/filters/tag/filter.go
new file mode 100644
index 0000000000000000000000000000000000000000..fdd8e4a1a9a02b9f5a3c1bfb3e5c31b65509a381
--- /dev/null
+++ b/lib/gat/handlers/discovery/discoverers/digitalocean/filters/tag/filter.go
@@ -0,0 +1,47 @@
+package tag
+
+import (
+	"github.com/caddyserver/caddy/v2"
+	"github.com/digitalocean/godo"
+
+	"gfx.cafe/gfx/pggat/lib/gat/handlers/discovery/discoverers/digitalocean"
+	"gfx.cafe/gfx/pggat/lib/util/strutil"
+)
+
+func init() {
+	caddy.RegisterModule((*Filter)(nil))
+}
+
+type Filter struct {
+	Tag strutil.Matcher `json:"tag"`
+}
+
+func (T *Filter) CaddyModule() caddy.ModuleInfo {
+	return caddy.ModuleInfo{
+		ID: "pggat.handlers.discovery.discoverers.digitalocean.filters.tag",
+		New: func() caddy.Module {
+			return new(Filter)
+		},
+	}
+}
+
+func (T *Filter) Allow(database godo.Database) bool {
+	for _, tag := range database.Tags {
+		if T.Tag.Matches(tag) {
+			return true
+		}
+	}
+	return false
+}
+
+func (T *Filter) AllowReplica(database godo.DatabaseReplica) bool {
+	for _, tag := range database.Tags {
+		if T.Tag.Matches(tag) {
+			return true
+		}
+	}
+	return false
+}
+
+var _ digitalocean.Filter = (*Filter)(nil)
+var _ caddy.Module = (*Filter)(nil)
diff --git a/lib/gat/standard/standard.go b/lib/gat/standard/standard.go
index 6529c3f3740b8ae3dcdbd76693c2f8d73fec291b..deb2deb9b05db103bbeb7a04a12f3b5f7ee44aca 100644
--- a/lib/gat/standard/standard.go
+++ b/lib/gat/standard/standard.go
@@ -34,6 +34,9 @@ import (
 	_ "gfx.cafe/gfx/pggat/lib/gat/handlers/discovery/discoverers/google_cloud_sql"
 	_ "gfx.cafe/gfx/pggat/lib/gat/handlers/discovery/discoverers/zalando_operator"
 
+	// digitalocean filters
+	_ "gfx.cafe/gfx/pggat/lib/gat/handlers/discovery/discoverers/digitalocean/filters/tag"
+
 	// poolers
 	_ "gfx.cafe/gfx/pggat/lib/gat/handlers/pool/poolers/lifo"
 	_ "gfx.cafe/gfx/pggat/lib/gat/handlers/pool/poolers/rob"
diff --git a/lib/rob/schedulers/v3/scheduler_test.go b/lib/rob/schedulers/v3/scheduler_test.go
index ea42e5021e4163037254ed45e8db891bd9214cf4..cef8f3a6dad27a1e4f9e165ccba20c0813095512 100644
--- a/lib/rob/schedulers/v3/scheduler_test.go
+++ b/lib/rob/schedulers/v3/scheduler_test.go
@@ -72,7 +72,7 @@ func testStarver(sched *Scheduler, tab *ShareTable, id int, dur time.Duration) {
 }
 
 func similar(v0, v1 int, vn ...int) bool {
-	const margin = 0.25 // 25% margin of error
+	const margin = 0.5 // 50% margin of error
 
 	minimum := v0
 	maximum := v0