ahmedmerza/watchtower
Ahmed Merza's Watchtower is a Laravel/PHP package for application monitoring and observability. It gives you a lightweight way to keep an eye on key events and health signals, helping you spot issues early and stay informed with simple setup.
Architecture fit. laravel-watchtower is a conventional Laravel package: service provider registration, a publishable config, publishable migrations under a vendor tag (watchtower-migrations), and artisan commands (watchtower:reconcile, watchtower:sync) that run on the scheduler. It owns one primary table (blacklisted_ips) and implements a master/satellite topology where satellite nodes push blocks to a master over signed, paged HTTP requests. v1.1.0 does not change that shape — it is a performance release (indexes) plus a correctness/volume fix in reconcile. That is a good sign: the package is being hardened against production-scale data (~50k rows) rather than expanding its surface area.
Integration feasibility. Very high. The release is additive: one migration, no config changes, no route or payload changes called out. Two steps: publish the migration and run it. The only behavioral change is a fail-fast path in watchtower:reconcile.
Technical risk.
CREATE INDEX (non-CONCURRENTLY) takes a SHARE lock that blocks writes for the duration, and SQLite blocks writers outright. On a table already holding tens of thousands of rows the build is likely seconds, not minutes, but this needs a runbook, not a blind php artisan migrate in a deploy hook. Confirm whether the package's migration uses CONCURRENTLY (Laravel's schema builder does not by default) and whether it ships a down() that drops the indexes.WATCHTOWER_MASTER_URL set but no WATCHTOWER_SYNC_SECRET previously queued jobs that logged warnings and sent nothing; now reconcile fails the laravel target with an explicit message. This is the right direction — it converts a silent no-op into a visible misconfiguration — but "fails the target" is an observable contract we need to see before deploying. If a health dashboard or alerting pipeline keys off target status, this will surface as a new alert on any misconfigured satellite. That is desirable, but it will look like a regression to on-call unless it is announced.watchtower:sync performs, in order to compute drift. Satellites previously only wrote; now they read. Master availability and read capacity become part of the reconcile critical path whenever a node has local blocks to compare. The release states a run with nothing of its own to repair doesn't contact the master at all, which bounds the blast radius but does not remove it.scope index. Rationale is sound (near-total selectivity makes the index useless on the common case). The residual risk is a deployment where most rows are not global — then reconcile and the sync pull do a sequential scan. Low probability given the stated workload, but worth pinning as an assumption.Key questions.
down()?PushBlockToMaster idempotent on the master side, and does the new drift check replace the deduplication the old per-row queueing implicitly lacked?Stack fit. Assumes a standard Laravel app with a relational database, a queue worker, and the scheduler running. The five-minute reconcile cadence and the job-based push imply a real queue; a sync queue driver would serialize reconcile and should be avoided. No new infrastructure is introduced — no cache, no broker, no external service beyond the master HTTP endpoint that already existed.
Migration path. Publish with php artisan vendor:publish --tag=watchtower-migrations, review the generated file (this is the point at which you verify index strategy and lock behavior), then php artisan migrate. Because the change is purely additive — new indexes, no column changes — deploy order is flexible: the migration can land before or with the application code, and old code benefits immediately from the faster pages. Running it ahead of the code deploy is the lower-risk ordering and lets you measure the page-load improvement independently of the reconcile change.
Compatibility. No config changes and no route contract changes (GET /blocks keeps its shape). The one compatibility break is environmental: WATCHTOWER_MASTER_URL without WATCHTOWER_SYNC_SECRET. Audit every satellite's environment for that combination before deploying. If any node is in that state, either set the secret or accept that its reconcile will begin reporting failure — which, again, reflects a configuration that never worked.
Sequencing.
WATCHTOWER_MASTER_URL and confirm WATCHTOWER_SYNC_SECRET is present and matches the master's.Maintenance. Vendor migrations are published into the application, so the index migration becomes yours to maintain; a future package release that redefines these indexes will require reconciling the two. Budget for a documented runbook entry on adding indexes to blacklisted_ips as it grows. The upside is that the migration is small, readable, and additive, so the long-term maintenance cost is near zero.
Support. This release trades two classes of silent failure for one loud one. The silent failures were: (a) unindexed page loads degrading management UI responsiveness as the blocklist grew, and (b) reconcile queueing jobs that logged a warning and delivered nothing on misconfigured satellites. Both are now addressed — the first by an order-of-magnitude latency drop (15–96 ms → <1 ms at 50k rows across SQLite, MySQL 8, Postgres 16), the second by an explicit target failure. Expect a short spike in misconfiguration reports immediately after deploy, then a decline in both "the block list page is slow" and "blocks aren't reaching the master" tickets.
Scaling. The most significant change is qualitative, not quantitative: reconcile's job volume now scales with drift instead of with the size of the blocklist. On a five-minute schedule, a 50k-row global blocklist previously produced a job per row per run; that is the kind of load that quietly saturates a worker pool and makes queue depth a meaningless signal. Removing it should materially reduce queue pressure and make reconcile observability trustworthy. The new cost is read traffic on the master: one paged signed read per satellite per run, bounded by the "no local blocks, no contact" rule. With N satellites at 288 runs/day, the master should see a predictable, low request rate — but it is a new read pattern on the master and should be monitored for the first week alongside its connection and query latency.
Failure modes.
scope distribution — if a deployment's data stops being overwhelmingly global, the deliberately unindexed scope filter becomes the bottleneck in reconcile and sync pull. Cheap to detect via slow-query logs; cheap to fix with an index if it ever happens.Ramp-up. Low. For an engineer who already operates the package, this is a migration and one environment-variable audit. For a team new to it, the master/satellite model and the reconcile-versus-sync distinction remain the conceptual load, and this release arguably makes that easier to teach: reconcile is now "repair drift," sync is "pull everything," and both fail visibly when misconfigured instead of half-working.
How can I help you explore Laravel packages today?