- Add gitignore and prettier configuration files for project scaffolding - Implement room access control service with project member verification - Create user access key management with CRUD operations and activity logging - Add accordion UI component for frontend expandable sections - Implement room AI configuration with list, upsert, and delete operations - Add AI event types for agent join/leave/status change tracking - Create streaming AI processing services for mode and react patterns - Build room AI service with model detection and idempotency handling - Integrate chat service orchestration for AI message processing - Add typing indicators and stream cancellation for AI interactions - Implement mention parsing and context extraction for AI agents
257 lines
7.5 KiB
Markdown
257 lines
7.5 KiB
Markdown
# Hook Queue NATS JetStream Migration Guide
|
|
|
|
## Overview
|
|
|
|
The git hook queue now supports both Redis Lists and NATS JetStream as backend message queues. This allows gradual migration from Redis to NATS without downtime.
|
|
|
|
## Architecture
|
|
|
|
### Producer (`ReceiveSyncService`)
|
|
|
|
The producer tries NATS first (if configured), then falls back to Redis:
|
|
|
|
```rust
|
|
pub struct ReceiveSyncService {
|
|
pool: deadpool_redis::cluster::Pool,
|
|
redis_prefix: String,
|
|
nats_publish: Option<Arc<dyn Fn(String, Vec<u8>) -> Pin<Box<dyn Future<Output = Result<u64>> + Send>> + Send + Sync>>,
|
|
}
|
|
```
|
|
|
|
### Consumer (`RedisConsumer`)
|
|
|
|
The consumer uses NATS if configured, otherwise falls back to Redis:
|
|
|
|
```rust
|
|
pub struct RedisConsumer {
|
|
pool: deadpool_redis::cluster::Pool,
|
|
prefix: String,
|
|
block_timeout_secs: u64,
|
|
nats_consume: Option<NatsHookConsumeFn>,
|
|
}
|
|
```
|
|
|
|
## Integration with AppTransport
|
|
|
|
### Producer Integration
|
|
|
|
```rust
|
|
use git::ssh::ReceiveSyncService;
|
|
use transport::AppTransport;
|
|
|
|
let transport = Arc::new(AppTransport::new(/* ... */));
|
|
|
|
// Create NATS publish function
|
|
let nats_publish = {
|
|
let transport = transport.clone();
|
|
Arc::new(move |subject: String, payload: Vec<u8>| {
|
|
let transport = transport.clone();
|
|
Box::pin(async move {
|
|
let ack = transport.publish(&subject, payload).await?;
|
|
Ok(ack.sequence)
|
|
}) as Pin<Box<dyn Future<Output = anyhow::Result<u64>> + Send>>
|
|
})
|
|
};
|
|
|
|
// Create service with NATS support
|
|
let sync_service = ReceiveSyncService::with_nats(redis_pool, nats_publish);
|
|
|
|
// Or use Redis-only mode
|
|
let sync_service = ReceiveSyncService::new(redis_pool);
|
|
```
|
|
|
|
### Consumer Integration
|
|
|
|
```rust
|
|
use git::hook::pool::redis::{RedisConsumer, NatsHookConsumeFn};
|
|
|
|
// Create NATS consume function
|
|
let nats_consume: NatsHookConsumeFn = {
|
|
let transport = transport.clone();
|
|
Arc::new(move |subject: String, batch_size: usize| {
|
|
let transport = transport.clone();
|
|
Box::pin(async move {
|
|
let mut results = Vec::new();
|
|
|
|
// Pull messages from JetStream consumer
|
|
for _ in 0..batch_size {
|
|
match transport.pull_one(&subject).await {
|
|
Ok(Some(msg)) => {
|
|
let data = msg.payload.to_vec();
|
|
let msg_clone = msg.clone();
|
|
let ack_fn = Box::new(move || {
|
|
let msg = msg_clone.clone();
|
|
Box::pin(async move {
|
|
msg.ack().await?;
|
|
Ok(())
|
|
}) as Pin<Box<dyn Future<Output = anyhow::Result<()>> + Send>>
|
|
});
|
|
results.push((data, ack_fn));
|
|
}
|
|
Ok(None) => break,
|
|
Err(e) => return Err(e),
|
|
}
|
|
}
|
|
|
|
Ok(results)
|
|
}) as Pin<Box<dyn Future<Output = anyhow::Result<Vec<(Vec<u8>, Box<dyn Fn() -> Pin<Box<dyn Future<Output = anyhow::Result<()>> + Send>> + Send>)>>> + Send>>
|
|
})
|
|
};
|
|
|
|
// Create consumer with NATS support
|
|
let consumer = RedisConsumer::with_nats(
|
|
redis_pool,
|
|
"{hook}".to_string(),
|
|
5, // block_timeout_secs
|
|
nats_consume,
|
|
);
|
|
|
|
// Or use Redis-only mode
|
|
let consumer = RedisConsumer::new(redis_pool, "{hook}".to_string(), 5);
|
|
```
|
|
|
|
## Queue Subjects
|
|
|
|
The hook queue uses the following NATS subjects:
|
|
|
|
- `queue.hook.sync` - Repository sync tasks (git push/pull operations)
|
|
|
|
Additional task types can be added by extending the subject pattern:
|
|
- `queue.hook.{task_type}` - Generic pattern for any hook task type
|
|
|
|
## Migration Strategy
|
|
|
|
### Phase 1: Dual Write (Current)
|
|
- Producer writes to both NATS and Redis
|
|
- Consumer reads from Redis only
|
|
- Zero risk, full rollback capability
|
|
|
|
### Phase 2: Dual Read
|
|
- Producer writes to both NATS and Redis
|
|
- Consumer reads from NATS, falls back to Redis on error
|
|
- Validates NATS consumer stability
|
|
|
|
### Phase 3: NATS Primary
|
|
- Producer writes to NATS only (Redis disabled)
|
|
- Consumer reads from NATS only
|
|
- Redis queue deprecated
|
|
|
|
### Phase 4: Redis Removal
|
|
- Remove Redis Lists code
|
|
- Remove `pool` parameter
|
|
- Simplify to NATS-only implementation
|
|
|
|
## NATS JetStream Setup
|
|
|
|
### Stream Configuration
|
|
|
|
```bash
|
|
nats stream add HOOK_QUEUE \
|
|
--subjects "queue.hook.>" \
|
|
--retention limits \
|
|
--max-msgs=-1 \
|
|
--max-age=7d \
|
|
--storage file \
|
|
--replicas 3
|
|
```
|
|
|
|
### Consumer Configuration
|
|
|
|
```bash
|
|
nats consumer add HOOK_QUEUE hook-sync-worker \
|
|
--filter "queue.hook.sync" \
|
|
--ack explicit \
|
|
--pull \
|
|
--deliver all \
|
|
--max-deliver 3 \
|
|
--max-pending 100
|
|
```
|
|
|
|
## Differences from Email Queue
|
|
|
|
### Redis Backend
|
|
- **Email Queue**: Uses Redis Streams (XADD/XREADGROUP)
|
|
- **Hook Queue**: Uses Redis Lists (LPUSH/BLMOVE)
|
|
|
|
### Atomicity
|
|
- **Email Queue**: Consumer group provides at-least-once delivery
|
|
- **Hook Queue**: BLMOVE provides atomic move-to-work-queue pattern
|
|
|
|
### Work Queue Pattern
|
|
- **Email Queue**: No work queue, relies on consumer group
|
|
- **Hook Queue**: Uses separate work queue (`{hook}:sync:work`) for in-flight tracking
|
|
|
|
### Acknowledgment
|
|
- **Email Queue**: XACK removes from pending entries list
|
|
- **Hook Queue**: LREM removes from work queue
|
|
|
|
### Retry Logic
|
|
- **Email Queue**: Automatic via consumer group pending entries
|
|
- **Hook Queue**: Manual via Lua script (LREM + LPUSH)
|
|
|
|
## Monitoring
|
|
|
|
### Logs
|
|
|
|
- NATS publish: `"hook task queued to NATS"`
|
|
- Redis publish: `"hook task queued to Redis"`
|
|
- NATS consume: `"task dequeued from NATS"`
|
|
- Redis consume: `"task dequeued"`
|
|
|
|
### Metrics
|
|
|
|
Add these metrics to track hook queue performance:
|
|
|
|
```rust
|
|
counter!("hook_task_queued_total", "backend" => "nats").increment(1);
|
|
counter!("hook_task_queued_total", "backend" => "redis").increment(1);
|
|
counter!("hook_task_consumed_total", "backend" => "nats").increment(1);
|
|
counter!("hook_task_consumed_total", "backend" => "redis").increment(1);
|
|
```
|
|
|
|
## Rollback
|
|
|
|
To disable NATS and return to Redis-only:
|
|
|
|
```rust
|
|
// Producer
|
|
let sync_service = ReceiveSyncService::new(redis_pool);
|
|
|
|
// Consumer
|
|
let consumer = RedisConsumer::new(redis_pool, "{hook}".to_string(), 5);
|
|
```
|
|
|
|
No code changes required, just use the `new()` constructor instead of `with_nats()`.
|
|
|
|
## Benefits
|
|
|
|
1. **Zero Downtime**: Gradual migration with fallback
|
|
2. **No Circular Dependency**: Uses function pointers instead of crate dependencies
|
|
3. **Backward Compatible**: Existing code works without changes
|
|
4. **Type Safe**: Compile-time guarantees for integration
|
|
5. **Observable**: Consistent logging for both backends
|
|
|
|
## Known Limitations
|
|
|
|
### NATS Acknowledgment Timing
|
|
|
|
The current implementation acks NATS messages immediately after deserialization, not after successful processing. This is different from the Redis pattern where:
|
|
|
|
- Redis: Task moves to work queue → processes → acks (removes from work queue)
|
|
- NATS: Task received → acks immediately → processes
|
|
|
|
**Future Enhancement**: Store ack functions in a map keyed by task ID, then call them after successful processing. This requires refactoring the worker loop to track pending acks.
|
|
|
|
### Work Queue Pattern
|
|
|
|
NATS JetStream doesn't have a direct equivalent to Redis's work queue pattern. The current implementation relies on JetStream's built-in redelivery mechanism instead of a separate work queue.
|
|
|
|
## Next Steps
|
|
|
|
1. Add NATS integration to `apps/git-hook/src/main.rs`
|
|
2. Add configuration flags for queue backend selection
|
|
3. Test dual-write mode in staging
|
|
4. Monitor NATS consumer stability
|
|
5. Implement proper ack-after-processing pattern
|
|
6. Add metrics for queue depth and processing latency
|