Files
ngit-grasp/docs/explanation/comparison.md
DanConwayDev 51751d59f3 Change default port from 8080 to 7334 (NGIT on phone keypad)
- Update default bind address in src/config.rs to 127.0.0.1:7334
- Update all four critical config sources per AGENTS.md:
  - src/config.rs (code default and tests)
  - .env.example (development template)
  - docs/reference/configuration.md (user documentation)
  - nix/module.nix (NixOS deployment)
- Update all documentation examples and references:
  - README.md (with note about phone keypad mnemonic)
  - docs/how-to/*.md (deploy, prometheus-setup, test-compliance)
  - docs/explanation/*.md (architecture, comparison)
  - docs/learnings/grasp-audit.md

Port 7334 spells NGIT on a phone keypad, making it memorable and
project-specific.

All tests pass (336 lib tests + 51 integration tests).
2026-01-12 22:51:43 +00:00

380 lines
22 KiB
Markdown

# ngit-grasp vs ngit-relay Comparison
This document compares ngit-grasp (this project) with ngit-relay (the reference implementation) based on their actual implementations.
## High-Level Overview
| Aspect | ngit-relay (Reference) | ngit-grasp (This Project) |
|--------|------------------------|---------------------------|
| **Language** | Go | Rust |
| **Architecture** | Multi-process (nginx + fcgiwrap + khatru + sync daemon) | Single integrated process |
| **Git Protocol** | git-http-backend (C via fcgiwrap) | HTTP layer in Rust + git subprocess |
| **Authorization** | Pre-receive Git hook | Inline HTTP handler validation |
| **Nostr Relay** | Khatru (Go library) | nostr-relay-builder (Rust library) |
| **Event Store** | Badger (Go KV database) | LMDB or NostrDB (Rust) |
| **Proactive Sync** | Git-only (polls DB + fetches from git servers) | Nostr event sync + git sync (event-driven) |
| **Process Management** | supervisord (4 processes) | Single tokio runtime |
| **Packaging** | Docker with supervisord | Single static binary or Docker |
| **Configuration** | Environment variables | Environment variables + CLI flags |
| **Total Code** | ~1,866 lines of Go | ~25,000 lines of Rust |
## Architecture Comparison
### ngit-relay (Multi-Process)
```
┌──────────────── Docker Container ────────────────┐
│ │
│ ┌─────────────────────────────────────────────┐ │
│ │ supervisord │ │
│ │ - fcgiwrap (git-http-backend wrapper) │ │
│ │ - nginx (HTTP + reverse proxy) │ │
│ │ - ngit-relay-khatru (Nostr relay) │ │
│ │ - ngit-relay-proactive-sync (sync daemon) │ │
│ └─────────────────────────────────────────────┘ │
│ │
│ ┌──────────┐ ┌────────────────────┐ │
│ │ nginx │────────▶│ git-http-backend │ │
│ │ :80 │ │ (C binary via CGI) │ │
│ └──────┬───┘ └──────────┬─────────┘ │
│ │ │ │
│ │ ▼ │
│ │ ┌──────────────────┐ │
│ │ │ Git Repos │ │
│ │ │ + pre-receive │ │
│ │ │ hook (Go) │ │
│ │ └────────┬─────────┘ │
│ │ │ WebSocket │
│ │ │ query │
│ │ ▼ │
│ │ ┌──────────────────┐ │
│ └──────────────▶│ Khatru Relay │ │
│ │ :3334 │ │
│ │ (Badger DB) │ │
│ └──────────────────┘ │
│ │
│ Separate sync daemon polls relay DB │
│ and fetches from remote git servers │
│ │
└───────────────────────────────────────────────────┘
```
### ngit-grasp (Single Process)
```
┌────────────── ngit-grasp (Single Binary) ─────────────┐
│ │
│ ┌──────────────────────────────────────────────────┐ │
│ │ hyper HTTP Server (:7334) │ │
│ │ - WebSocket upgrade for Nostr relay │ │
│ │ - Git Smart HTTP handlers │ │
│ │ - Landing page + metrics endpoint │ │
│ └───────┬──────────────────────┬───────────────────┘ │
│ │ │ │
│ ▼ ▼ │
│ ┌──────────────┐ ┌────────────────────┐ │
│ │ Git Handlers │ │ Nostr Relay │ │
│ │ (HTTP layer) │ │ (nostr-relay- │ │
│ │ │ │ builder library) │ │
│ │ - info/refs │ │ - NIP-34 Policy │ │
│ │ - upload-pk │◀─────┤ (inline query) │ │
│ │ - receive-pk │ auth │ - LMDB/NostrDB │ │
│ │ + inline │ check│ - WebSocket │ │
│ │ validation │ │ - NIP-11 endpoint │ │
│ └──────┬───────┘ └──────────┬─────────┘ │
│ │ │ │
│ ▼ ▼ │
│ ┌──────────────┐ ┌────────────────────┐ │
│ │ git binary │ │ Purgatory │ │
│ │ upload-pack │ │ (in-memory queue) │ │
│ │ receive-pk │ │ + sync loop │ │
│ └──────────────┘ └────────────────────┘ │
│ │
│ ┌──────────────────────────────────────────────────┐ │
│ │ SyncManager (tokio background task) │ │
│ │ - Multi-relay Nostr event sync (GRASP-02) │ │
│ │ - Negentropy + REQ/EOSE support │ │
│ │ - Health tracking & exponential backoff │ │
│ │ - Git fetch from remote servers (via purgatory) │ │
│ └──────────────────────────────────────────────────┘ │
│ │
│ ┌──────────────────────────────────────────────────┐ │
│ │ Shared State (Arc<T>) │ │
│ │ - Database (LMDB/NostrDB/Memory) │ │
│ │ - Purgatory (DashMap - concurrent queue) │ │
│ │ - Metrics (Prometheus) │ │
│ └──────────────────────────────────────────────────┘ │
│ │
└────────────────────────────────────────────────────────┘
```
## Feature Comparison
### Key Architectural Difference: Nostr Event Sync
**The biggest difference between the two implementations is how they handle Nostr events:**
| Aspect | ngit-relay | ngit-grasp |
|--------|-----------|-----------|
| **Event Arrival** | Relies on clients to push events directly | Proactively syncs events from other relays |
| **Discovery** | None - only stores what clients send | Discovers events from relay network |
| **Coordination** | Events and git data handled separately | Purgatory coordinates events + git data |
| **Completeness** | May miss events if clients don't push to this relay | Actively fetches missing events from network |
| **Implementation** | No event sync code (~0 lines) | Full multi-relay sync system (~5,000 lines) |
**Example scenario:**
- User creates PR on relay A, pushes git data to server B
- **ngit-relay**: Only knows about events/data pushed directly to it
- **ngit-grasp**: Discovers PR event from relay A, fetches git data from server B
This is why ngit-grasp has ~13x more code - the majority is implementing GRASP-02 proactive event sync.
### Git Protocol Implementation
| Feature | ngit-relay | ngit-grasp |
|---------|-----------|-----------|
| **HTTP Server** | nginx | hyper (Rust) |
| **Git Backend** | git-http-backend (C) via fcgiwrap | HTTP protocol layer (Rust) + git binary |
| **Process Model** | FastCGI spawns git-http-backend | HTTP handler spawns git subprocess |
| **Upload Pack** | C binary passthrough | Rust parses HTTP → spawns `git upload-pack` |
| **Receive Pack** | C binary → pre-receive hook | Rust validates → spawns `git receive-pack` |
| **Authorization** | Go hook queries relay via WebSocket | In-process function call before git spawn |
| **Error Reporting** | Hook stderr → git client | HTTP response body (before git runs) |
| **CORS** | nginx config | hyper middleware |
| **Lines of Code** | ~0 (uses C binary) + hook ~135 | ~1,000+ (HTTP protocol layer) |
### Authorization Logic
| Feature | ngit-relay | ngit-grasp |
|---------|-----------|-----------|
| **Location** | pre-receive hook (separate Go binary) | Inline HTTP handler (Rust) |
| **Trigger** | Git invokes hook during push | HTTP handler before spawning git |
| **State Query** | WebSocket to localhost:3334 | Direct database query (in-process) |
| **Latency** | +50-100ms (hook spawn + WS query) | +10-20ms (function call) |
| **Error Channel** | stderr → git client | HTTP 403 response |
| **Ref Parsing** | Read from stdin (hook protocol) | Parse from HTTP request body |
| **Maintainer Resolution** | Recursive Go function | Recursive Rust function (similar) |
| **State Caching** | None (queries relay per push) | Purgatory tracks pending events |
### Nostr Relay
| Feature | ngit-relay | ngit-grasp |
|---------|-----------|-----------|
| **Implementation** | Khatru (Go library) | nostr-relay-builder (Rust library) |
| **Database** | Badger (Go KV store) | LMDB or NostrDB (Rust) |
| **Process** | Separate process on :3334 | Integrated (same binary) |
| **Policies** | Go functions in `policies.go` | Rust traits (modular sub-policies) |
| **Event Validation** | Single function with branches | 4 separate policy modules |
| **WebSocket** | Khatru built-in | nostr-relay-builder + hyper |
| **NIP-11** | Manual JSON in code | Built-in support from library |
| **Connection** | Separate from HTTP | Shared hyper server |
| **Lines of Code** | ~186 (policies.go) + Khatru library | ~3,000+ (policy modules) + nostr-relay-builder library |
### Proactive Sync
| Feature | ngit-relay | ngit-grasp |
|---------|-----------|-----------|
| **Architecture** | Separate daemon (`ngit-relay-proactive-sync`) | Integrated SyncManager (tokio task) |
| **Nostr Event Sync** | ❌ None (relies on client pushes) | ✅ Multi-relay sync with negentropy/REQ |
| **Git Data Sync** | ✅ Polls local DB + fetches from git servers | ✅ Event-driven via purgatory queue |
| **Sync Trigger** | Timer (every 15 minutes) | Immediate on event arrival + timer for retries |
| **Relay Discovery** | N/A (no event sync) | Dynamic from 30617 announcement events |
| **Protocol** | Git fetch only | Nostr WebSocket + git fetch |
| **Concurrency** | Goroutines (per-repo iteration) | Tokio async tasks (per-relay connections) |
| **Health Tracking** | Basic retry on git fetch failures | RelayHealthTracker with exponential backoff |
| **Connection Management** | N/A (no Nostr connections) | Persistent connections with reconnect |
| **Coordination** | Separate process | Purgatory + SyncManager coordination |
| **Lines of Code** | ~112 (main.go) + ~305 (git sync) | ~5,000+ (Nostr sync + git sync + coordination) |
### Repository Management
| Feature | ngit-relay | ngit-grasp |
|---------|-----------|-----------|
| **Creation** | Event hook → shell commands | Event hook → tokio::process |
| **Trigger** | `EventReceiveHook()` in Go | `handle_announcement()` in Rust |
| **Configuration** | `git config` via shell | `git config` via tokio::process |
| **Hook Installation** | Symlinks to pre-receive/post-receive | Not needed (inline auth) |
| **Permissions** | `chown nginx:nginx` | tokio::fs permissions |
| **Path Structure** | `<npub>/<id>.git` | `<npub>/<id>.git` (same) |
### Event Coordination (Purgatory)
| Feature | ngit-relay | ngit-grasp |
|---------|-----------|-----------|
| **Implementation** | None | Dedicated Purgatory system |
| **Purpose** | N/A | Solves "which arrives first?" problem |
| **Storage** | N/A | In-memory DashMap (thread-safe) |
| **Expiry** | N/A | 30 minutes default TTL |
| **State Events** | Accepted (git sync happens later via timer) | Queued until git data arrives |
| **PR Events** | Accepted (references may be missing) | Queued with placeholder refs |
| **Sync Queue** | Timer-based (polls all repos) | Event-driven (only syncs needed repos) |
| **Cleanup** | N/A | Background task (60s interval) |
| **Lines of Code** | 0 | ~2,000+ |
**Impact**: ngit-relay accepts all events and relies on periodic sync to eventually fetch git data. ngit-grasp holds events in purgatory and triggers targeted syncs, providing faster convergence and better coordination between Nostr events and git data.
### Deployment & Operations
| Feature | ngit-relay | ngit-grasp |
|---------|-----------|-----------|
| **Dependencies** | nginx, git, fcgiwrap, supervisord, Go runtime | git, Rust binary (statically linked) |
| **Process Count** | 4 (supervisord + nginx + khatru + sync) | 1 (single tokio runtime) |
| **Configuration** | `.env` file | `.env` + CLI flags (clap) |
| **Docker Image Size** | ~500MB (Alpine + tools + Go runtime) | ~100MB (Debian slim + git + binary) |
| **Startup Time** | ~2-5 seconds (multiple processes) | ~0.5 seconds (single process) |
| **Memory (Idle)** | ~150-200MB (4 processes + Go GC) | ~50-100MB (single process, no GC) |
| **Logs** | supervisord → stdout (4 streams) | tracing → stdout (unified) |
| **Monitoring** | None built-in | Prometheus metrics endpoint |
| **Binary Distribution** | Docker only | Native binary + Docker |
### Development Experience
| Feature | ngit-relay | ngit-grasp |
|---------|-----------|-----------|
| **Build Time** | Fast (~5s incremental, Go) | Slow first build (~5min), fast incremental |
| **Type Safety** | Good (Go interfaces) | Excellent (Rust traits + ownership) |
| **Testing** | Go tests + shell scripts | Rust unit + integration tests |
| **Test Relay** | Manual Docker setup | `TestRelay` fixture (auto-start binary) |
| **Debugging** | Multi-process (harder) | Single process (easier) |
| **IDE Support** | Good (gopls) | Excellent (rust-analyzer) |
| **Async Model** | Goroutines (simple) | Tokio (more complex) |
| **Error Handling** | `error` interface + if checks | Result<T, E> + `?` operator |
| **Dependencies** | Go modules | Cargo crates (larger ecosystem) |
### Code Complexity
| Component | ngit-relay | ngit-grasp | Notes |
|-----------|-----------|-----------|-------|
| Main server | 129 | 196 | ngit-relay uses supervisord |
| Git HTTP protocol | 0 (C binary via fcgiwrap) | ~1,000 | ngit-grasp implements HTTP layer |
| Auth logic (hooks) | 135 + 52 | 0 | ngit-grasp inline, no hooks |
| Auth logic (inline) | 0 | ~800 | ngit-grasp authorization module |
| Nostr relay policies | 186 | ~3,000 | Both use libraries (Khatru vs nostr-relay-builder) |
| Git-only proactive sync | 112 + 305 | 0 | ngit-relay git sync only |
| Nostr event proactive sync | 0 | ~5,000 | ngit-grasp adds full event sync (GRASP-02 v4) |
| Purgatory coordination | 0 | ~2,000 | ngit-grasp event/git coordination |
| Shared utils | 241 + 132 | ~4,000 | ngit-grasp more comprehensive |
| Config | ~50 | ~400 | ngit-grasp CLI + validation |
| Metrics | 0 | ~1,500 | ngit-grasp Prometheus |
| **Total** | **~1,866** | **~25,000** | ngit-grasp 13x more code |
**Why the difference?**
- **Nostr event sync**: ngit-relay has NONE, ngit-grasp implements full multi-relay event sync (~5,000 lines)
- **Git HTTP protocol**: ngit-relay uses C binary, ngit-grasp implements HTTP layer (~1,000 lines)
- **Purgatory coordination**: ngit-grasp adds event/git coordination system (~2,000 lines)
- **Metrics & observability**: ngit-grasp includes comprehensive monitoring (~1,500 lines)
- Both use relay libraries (Khatru vs nostr-relay-builder), but ngit-grasp has more modular policies
### Performance Characteristics (Estimated)
| Metric | ngit-relay | ngit-grasp | Notes |
|--------|-----------|-----------|-------|
| **Startup** | ~2-5s | ~0.5s | Single process vs multi-process |
| **Memory (Idle)** | ~150MB | ~75MB | No GC, single process |
| **Memory (Active)** | ~200MB+ | ~100-150MB | Depends on event volume |
| **CPU (Idle)** | ~1-2% | ~0.5% | Fewer processes |
| **Push Latency** | +50-100ms | +10-20ms | No hook spawn overhead |
| **Clone Latency** | ~same | ~same | Both passthrough to git |
| **Concurrent Pushes** | Good (goroutines) | Excellent (tokio async) |
| **Event Ingestion** | Good (Badger) | Excellent (LMDB zero-copy) |
| **Sync Throughput** | Moderate (polling) | High (negentropy + async) |
*These are estimates based on architecture. Actual performance depends on workload.*
## Migration Path
For users of ngit-relay, migration to ngit-grasp involves:
### Data Migration
1. **Events**: Export from Badger → Import to LMDB/NostrDB
- No direct migration tool yet (would need to be built)
- Alternative: Use proactive sync to re-fetch from other relays
2. **Git Repositories**: Direct copy (same structure)
```bash
cp -r /srv/ngit-relay/repos/* /path/to/ngit-grasp/data/git/
```
3. **Configuration**: Translate environment variables
- Most variables are compatible (`NGIT_DOMAIN`, etc.)
- Remove nginx/supervisord-specific configs
### Compatibility
- **Git Data**: 100% compatible (same repository structure)
- **Nostr Events**: 100% compatible (standard NIP-34)
- **HTTP URLs**: Compatible (same path structure)
- **Git Hooks**: ngit-grasp doesn't use hooks (inline auth instead)
### Downtime
- Option 1: Run both in parallel (different domains), gradually migrate
- Option 2: Short downtime for data copy + config update
## When to Choose Each
### Choose ngit-relay (Reference) if:
- ✅ You need proven, production-tested code
- ✅ You're already familiar with Go ecosystem
- ✅ You prefer simple, minimal codebases (~1,866 lines)
- ✅ You trust battle-tested C binaries (git-http-backend)
- ✅ You want to stay close to the reference implementation
- ✅ You need to deploy immediately without complexity
- ✅ Your users will push events directly to your relay (no sync needed)
- ✅ You only need git data sync, not Nostr event sync
### Choose ngit-grasp (This Project) if:
- ✅ **You need Nostr event sync from other relays** (the main differentiator)
- ✅ You want better performance and lower resource usage
- ✅ You prefer Rust's type safety and memory safety
- ✅ You want simpler deployment (single binary, no supervisord)
- ✅ You need event/git data coordination (purgatory)
- ✅ You want inline authorization (lower latency)
- ✅ You need comprehensive observability (Prometheus metrics)
- ✅ You're comfortable with more complex codebase (~25,000 lines)
- ✅ You want full GRASP-02 v4 multi-relay event discovery
## Current Status
### ngit-relay (Reference)
- ✅ GRASP-01 complete and production-ready
- ✅ Git data proactive sync (fetches from git servers)
- ❌ No Nostr event sync (relies on client pushes)
- ✅ Battle-tested in production
- 🔄 Community adoption growing
### ngit-grasp (This Project)
- ✅ GRASP-01 complete with comprehensive testing
- ✅ GRASP-02 v4 multi-relay Nostr event sync with negentropy
- ✅ Git data proactive sync (via purgatory queue)
- ✅ Purgatory system for event/git coordination
- ✅ Prometheus metrics and health tracking
- ✅ NIP-77 negentropy support
- ✅ Full integration test suite
- 🔄 Production deployment validation ongoing
## Conclusion
Both implementations are valid approaches to GRASP with different philosophies:
- **ngit-relay** prioritizes simplicity - clients push events, relay syncs git data (~1,866 lines)
- **ngit-grasp** prioritizes completeness - syncs both events and git data from network (~25,000 lines)
**The fundamental difference**: ngit-relay expects clients to push Nostr events to it. ngit-grasp proactively discovers and syncs events from other relays in the network.
The choice depends on your priorities:
| Priority | Recommendation |
|----------|---------------|
| **Simplicity** | ngit-relay |
| **Event Discovery** | ngit-grasp (syncs from network) |
| **Production Stability** | ngit-relay (more battle-tested) |
| **Event Completeness** | ngit-grasp (proactive sync) |
| **Low Resources** | ngit-grasp (single binary, lower memory) |
| **Quick Deploy** | ngit-relay (Docker Compose) |
| **Development** | ngit-grasp (better tooling, type safety) |
| **Network Resilience** | ngit-grasp (multi-relay sync) |
For deployments where **Nostr event sync** is important (discovering events from other relays), **ngit-grasp** is required. For simpler deployments where users will push events directly, **ngit-relay** is sufficient and battle-tested.