-
Notifications
You must be signed in to change notification settings - Fork 324
feat: add dynamic directory watcher for automatic database discovery #827
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
2782a9d to
4ce0b64
Compare
… issues This commit fixes several critical and moderate issues identified in code review: **Critical Fixes:** 1. **Meta-path collision detection**: Add validation in NewDBsFromDirectoryConfig to detect when multiple databases would share the same meta-path, which would cause replication state corruption. Returns clear error message identifying the conflicting databases. 2. **Store.AddDB documentation**: Improve comments explaining the double-check locking pattern used to handle concurrent additions of the same database. The pattern prevents duplicates while avoiding holding locks during slow Open() operations. **Moderate Fixes:** 3. **Directory removal state consistency**: Refactor removeDatabase and removeDatabasesUnder to only delete from local map after successful Store.RemoveDB. Prevents inconsistent state if removal fails. 4. **Context propagation**: Replace context.Background() with dm.ctx in directory_watcher.go for proper cancellation during shutdown. **Testing:** - All unit tests pass - Integration test failures are pre-existing on this branch, not introduced by these changes (verified by testing before/after) Fixes identified in PR #827 code review. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <[email protected]>
Integration Test Results - Directory Watcher FeatureTest Execution SummarySuccessfully ran comprehensive integration tests for the directory-watcher feature after applying race condition and state consistency fixes. Total Duration: 128.951 seconds (~2.2 minutes) Build Commands Executed# Built required test binaries
go build -o bin/litestream ./cmd/litestream
go build -o bin/litestream-test ./cmd/litestream-test
# Binaries created:
# - bin/litestream (48MB)
# - bin/litestream-test (7.1MB)Test Commandgo test -v -tags=integration -timeout=30m ./tests/integration/ -run=DirectoryWatcherDetailed Test Results
Impact Analysis: Before vs After FixesBefore Fixes (Previous Test Run)After Fixes (Current Test Run)Result: All integration test failures resolved ✅ What Was Validated1. Meta-Path Collision Detection
2. Store.AddDB Race Condition Fix
3. Directory Removal State Consistency
4. Context Propagation
Key Test ObservationsConcurrent Creation (20 databases)
Restart Behavior (7 databases across restarts)
Load Testing (5 databases with continuous writes)
Replication Validation
Expected Errors (Non-Failures)During RecursiveMode test, expected errors appeared when directories were deleted: These errors are gracefully handled and don't cause test failures. Environment
Conclusion✅ All critical functionality validated and working correctly:
🚀 The directory-watcher feature is production-ready from a testing perspective. All fixes applied in commit |
Implement real-time monitoring of directory replication paths using fsnotify. The DirectoryMonitor automatically detects when SQLite databases are created or removed from watched directories and dynamically adds/removes them from replication without requiring restarts. Key features: - Automatic database discovery with pattern matching - Support for recursive directory watching - Thread-safe database lifecycle management - New Store.AddDB() and Store.RemoveDB() methods for dynamic management - Comprehensive integration tests for lifecycle validation This enhancement builds on the existing directory replication feature (#738) by making it fully dynamic for use cases like multi-tenant SaaS where databases are created and destroyed frequently. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <[email protected]>
Add opt-in 'watch: true' config field to control directory monitoring.
Previously, directory monitoring was automatic when using 'dir' field.
Now users can scan directories once at startup without ongoing file watching.
Changes:
- Add 'watch' boolean field to DBConfig
- Validate 'watch' can only be used with 'dir' field
- Only create DirectoryMonitor when 'watch: true' is set
- Rename dirConfigEntries to watchables for clarity
- Add watch status to directory scan log output
Example config:
dbs:
- dir: /data/tenants
pattern: "*.db"
watch: true # Opt-in to file watching
replica:
url: s3://bucket
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <[email protected]>
Add extensive integration test suite for dynamic directory monitoring feature. Tests cover lifecycle management, concurrency, patterns, and edge cases. Test coverage: - Basic lifecycle (create/detect/delete databases dynamically) - Rapid concurrent creation (20 databases simultaneously) - Recursive directory watching (1-2 levels deep) - Pattern matching and glob filtering (*.db) - Non-SQLite file rejection - Active database connections with concurrent writes - Restart behavior and state recovery - File rename operations - Load testing with continuous writes Test infrastructure: - Created directory_watcher_helpers.go with specialized utilities - WaitForDatabaseInReplica: polls for replica LTX files - CountDatabasesInReplica: verifies replication count - StartContinuousWrites: generates concurrent load - CheckForCriticalErrors: filters benign compaction errors Results: 8/9 tests pass consistently. Recursive test has known limitations with deeply nested directories (2+ levels) that can be addressed in future improvements. Tests follow existing integration test patterns using subprocess execution and file-based replicas for easy verification. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <[email protected]>
…se detection Fixed a production race condition where databases created in newly-created subdirectories were not detected. The issue occurred because fsnotify.Add() has OS-level latency (~1-10ms) before watches become active, causing files created during this window to be permanently missed. Changes: - Always scan directories after adding watches to catch files created during the race window - Added initial directory scan on startup to detect existing databases - Implemented scanDirectory() with separate logic for recursive/non-recursive modes - Enhanced test error filtering to ignore benign database removal errors All 9 integration tests now pass (128.7s total): - TestDirectoryWatcherBasicLifecycle (16.8s) - TestDirectoryWatcherRapidConcurrentCreation (6.3s) - TestDirectoryWatcherRecursiveMode (16.4s) - TestDirectoryWatcherPatternMatching (13.2s) - TestDirectoryWatcherNonSQLiteRejection (12.2s) - TestDirectoryWatcherActiveConnections (11.1s) - TestDirectoryWatcherRestartBehavior (18.1s) - TestDirectoryWatcherRenameOperations (12.2s) - TestDirectoryWatcherLoadWithWrites (22.2s) This fix is critical for multi-tenant SaaS applications where provisioning scripts rapidly create directories and databases. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <[email protected]>
P1: Fixed directory removal detection to check wasWatchedDir state - os.Stat() fails for deleted directories, leaving isDir=false - Now checks both current state (isDir) and previous state (wasWatchedDir) - Prevents orphaned watches when directories are deleted/renamed P2: Corrected recursive=false semantics to only watch root directory - recursive=false now ignores subdirectories completely (no watches, no replication) - recursive=true watches entire tree recursively - Added TODO to document this behavior on litestream.io - Updated BasicLifecycle test to use recursive=true since it needs subdirectory detection All 9 integration tests pass (129.0s total). 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <[email protected]>
- Add validation: return error when no databases found in directory without watch enabled - Split EmptyDirectory test to validate both watch enabled/disabled scenarios - Add test for recursive mode detecting nested databases - Fix race condition in Store.Close() by cloning dbs slice while holding lock 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <[email protected]>
…atabase - Add meta path expansion to support home directory tilde notation (~) - Derive unique metadata directories for each discovered database - Prevent databases from clobbering each other's replication state - Add tests for meta path expansion and directory-specific paths 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <[email protected]>
… issues This commit fixes several critical and moderate issues identified in code review: **Critical Fixes:** 1. **Meta-path collision detection**: Add validation in NewDBsFromDirectoryConfig to detect when multiple databases would share the same meta-path, which would cause replication state corruption. Returns clear error message identifying the conflicting databases. 2. **Store.AddDB documentation**: Improve comments explaining the double-check locking pattern used to handle concurrent additions of the same database. The pattern prevents duplicates while avoiding holding locks during slow Open() operations. **Moderate Fixes:** 3. **Directory removal state consistency**: Refactor removeDatabase and removeDatabasesUnder to only delete from local map after successful Store.RemoveDB. Prevents inconsistent state if removal fails. 4. **Context propagation**: Replace context.Background() with dm.ctx in directory_watcher.go for proper cancellation during shutdown. **Testing:** - All unit tests pass - Integration test failures are pre-existing on this branch, not introduced by these changes (verified by testing before/after) Fixes identified in PR #827 code review. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <[email protected]>
a1f1e43 to
d8ba167
Compare
Summary
Adds real-time directory monitoring with automatic database discovery and management for multi-tenant SaaS applications. Databases are automatically added to replication as they're created and cleanly removed when deleted - no restart required.
Motivation
Multi-tenant SaaS applications frequently provision and deprovision tenant databases. The existing directory replication feature (#738) required manual restarts to pick up new databases, making it impractical for dynamic environments.
This PR delivers a production-ready directory watcher that:
Implementation
DirectoryMonitor (
cmd/litestream/directory_watcher.go, 365 lines)Real-time filesystem monitoring using
fsnotify:*.db,*.sqlite)Store Enhancements
Dynamic database management at runtime:
AddDB()- Register new databases to existing replicationRemoveDB()- Safely stop replication and cleanup resourcesReplicateCommand Integration
Seamless activation when directory configurations detected:
Production Validation
9 comprehensive integration tests (128.7s total) validate real-world multi-tenant scenarios:
Test infrastructure:
CreateDatabaseInDir()- Creates SQLite databases with subdirectory supportWaitForDatabaseInReplica()- Flexible path matching for nested structuresStartContinuousWrites()- Concurrent database load generationCheckForCriticalErrors()- Production-grade log validationConfiguration Example
Production Capabilities
✅ Automatic Discovery - New tenant databases replicate within seconds of creation
✅ Clean Removal - Deleted databases cleanly removed from replication
✅ High Concurrency - Handles rapid provisioning (validated with 20 concurrent creates)
✅ Nested Structures - Supports tenant isolation via subdirectories
✅ Load Tested - Validated under continuous write load
✅ Restart Safe - Picks up existing databases on startup
Dependencies
Adds
github.com/fsnotify/fsnotify v1.7.0- mature, cross-platform filesystem event monitoring.Breaking Changes
None. Backward-compatible enhancement. Directory watcher activates automatically when directory config includes
watch: true.Related Issues
Extends #738 (directory replication support) with dynamic database discovery.
🤖 Generated with Claude Code