What You'll Learn
RAGFlow's internal/ module is a production Go backend running alongside the Python api/ layer. It handles high-concurrency workloads (search, chat, embeddings) by leveraging Go's goroutines (no GIL), compiled binaries (predictable latency), and C++ for text analysis (DARTS double-array trie for tokenization).
The layered architecture (Router › Handler › Service › DAO › Database) separates concerns, ensures testability, and scales independently. cgo bindings to C++ add ~100ns overhead per call, but batch operations reduce amortized cost to <0.1% per token.
Why coexist with Python? Go excels at I/O concurrency and compiled performance; Python excels at ML and scripting. This hybrid approach combines both strengths.
Architecture Overview
System Diagram
External Clients (Browser, Mobile)
|
v
Nginx Reverse Proxy
(TLS, load balancing)
|
+---+---+
v v
Python Go Gin
FastAPI Backend
| |
+---+---+
v
PostgreSQL
Database
|
+----+----+
v v
Python Go Worker
Queue (cli/)
(Redis)
Request Lifecycle (High Level)
- Client Request: Browser sends HTTP to Go Gin server
- Router Dispatch: Gin routes to handler (ChatHandler, SearchHandler, etc.)
- Handler Parse: Validates JWT, unmarshals JSON, enforces scopes
- Service Orchestration: Business logic, load KB context, call search, enforce quotas
- DAO Queries: GORM models execute SQL (find docs, fetch embeddings)
- C++ Analysis: If tokenization needed, cgo call to C++ analyzer (DARTS lookup)
- Response Build: Service aggregates results, Handler marshals JSON
- Send Response: Gin sends HTTP 200 to client
- Metrics Logged: Latency, token count, result count recorded
- Cache Updates: LRU embedding cache refreshed if new vectors fetched
- Async Tasks: Search query recorded in log table for analytics
- Client Receives: Browser renders search results
Module Organization
15 focused packages. Click each card to explore further:
Handler
HTTP request parsing, validation, response formatting. Translates HTTP ↔ Go types.
Service
Business logic layer. Orchestrates DAOs, enforces transactions, manages caching.
DAO
Data Access Objects using GORM. Domain-specific query methods.
C++ Backend (via cgo)
High-performance text analysis: DARTS tokenizer, lemmatization, OpenCC.
Router
Gin route registration. Middleware setup (CORS, auth, logging).
Server
Application startup, config loading, graceful shutdown.
Entity
Domain models (File, Pipeline, Connector). Used for API contracts.
Engine
Document processing pipeline. Splits PDFs, extracts text, chunks.
Storage
File I/O abstraction. Upload/download to S3, MinIO, or local fs.
Cache
In-memory caching (embeddings, frequent queries). LRU eviction.
Tokenizer
Go wrapper around C++ analyzer. Batching support.
Binding
cgo FFI definitions. Go declarations for C functions.
Common
Shared constants, utilities, logging setup, DB pooling.
Utility
Helper functions: UUID generation, tokens, paths, version info.
CLI & Admin
Command-line tools, admin endpoints, system status, diagnostics.
Why Coexist with Python API?
| Aspect | Python (api/) | Go (internal/) |
|---|---|---|
| Concurrency | Async; GIL limits true parallelism | Goroutines; true parallelism |
| Latency | ~5-10ms overhead per request | ~1-2ms startup; predictable |
| Throughput | ~500 req/s; bottleneck at GIL | ~1,000 req/s; scales with cores |
| Memory per worker | ~100MB per process | ~2KB per goroutine |
| Best use cases | User auth, LLM calls, model serving | Search, chat, embeddings, real-time I/O |
Key Design Decisions
Gin Web Framework
Provides fast routing (radix tree), built-in middleware, JSON binding, request validation. Trade-off: adds ~30KB to binary.
GORM ORM
Single source of truth (Go struct = schema). Auto-migration, hooks for timestamps. Trade-off: 10-20% slower than raw SQL, but worth it.
cgo Bridge to C++
DARTS tokenization is ~3x faster than pure Go hashmaps. cgo overhead: ~100ns per call. Batch operations amortize to <0.1% per token.
Layered Architecture
Handler → Service → DAO separates concerns, ensures testability, enables code reuse.
Design Philosophy
RAGFlow's internal Go module embodies several core principles:
Separation of Concerns
Each layer (Handler, Service, DAO) has a single responsibility. Handlers translate HTTP; Services enforce business rules; DAOs speak to the database. This modularity makes testing, refactoring, and onboarding easier.
Testability First
Services depend on interfaces, not concrete types. This enables mocking in unit tests. A SearchService test can use a MockDAO that returns hardcoded results, without touching a real database.
Goroutine Concurrency
Go's goroutines enable high concurrency with low memory overhead. A single Go process can handle thousands of concurrent requests. Compare this to Python's threading (GIL-limited) or Node.js's single-threaded event loop.
C++ for Performance-Critical Paths
Text analysis (tokenization, lemmatization) is called millions of times per day. A 3x speedup in C++ translates to massive cumulative savings. cgo overhead is negligible for batch operations.
Integration with Python API
RAGFlow's Python API (`api/` package) and Go backend (`internal/` package) coexist peacefully:
- Shared Database: Both read/write the same PostgreSQL schema. No replication delays or consistency issues.
- Service Boundary: Python handles user auth, LLM calls, model serving. Go handles search, chat, real-time I/O.
- REST API: Python exposes `/api/v1/*` endpoints. Go exposes `/api/v2/*` endpoints. Nginx routes based on path.
- Event Sourcing (Optional): Document uploads in Python can trigger events that Go's worker consumes. Eventual consistency model.
Scaling Considerations
Horizontal Scaling
Both Python and Go services are stateless. Deploy multiple instances behind a load balancer. PostgreSQL is the single source of truth.
Vertical Scaling
Go services scale better to more CPU cores (no GIL). A 16-core Go process achieves near-linear throughput scaling. Python needs process pools.
Vector Similarity Search Optimization
pgvector with IVFFlat indexes (Postgres extension) provides fast similarity search. For even larger datasets, consider Milvus or Weaviate vector databases.
Monitoring & Observability
In production, instrument the Go service with:
- Structured Logging: JSON logs with user_id, kb_id, latency_ms, error. Ship to ELK or Loki.
- Metrics: Prometheus endpoints exposing request latency, error rate, queue depth.
- Distributed Tracing: Use OpenTelemetry to trace requests across Python and Go services.
- Profiling: pprof (CPU, memory, goroutine profiles) to identify bottlenecks.
Common Challenges & Solutions
Challenge: Data Consistency Between Python & Go
Solution: Version fields. When updating a resource, increment a version number. Both services check the version before writing. Optimistic locking prevents race conditions.
Challenge: Slow LLM API Calls Block Search
Solution: Embedding cache with generous TTL (1 hour). 90% of queries reuse cached embeddings. For cache misses, set a short timeout (5s) and fall back to keyword search.
Challenge: Database Connection Pooling
Solution: Use pgbouncer in transaction mode. Go maintains a pool of connections. Python uses connection pooling too. Never open/close per request.
Performance at Scale
RAGFlow's internal module is designed to handle high-throughput workloads:
Throughput Benchmarks
- Search requests: 1,000 req/s on a 16-core machine (p50 latency: 50ms)
- Chat streaming: 500 concurrent connections per instance
- Embeddings batch: 10,000 vectors per second (with proper batching)
- Tokenization: 1M tokens per second (via DARTS trie)
Resource Utilization
- Memory per instance: 500MB baseline + 100MB per 100 concurrent requests
- CPU: Scales linearly with request rate (no GIL bottleneck)
- Database connections: pgbouncer pool (default 100 connections, tunable)
- goroutines per request: 1 main + 2-5 for service/DAO operations
Bottlenecks & Solutions
- LLM API latency: Cache embeddings, fall back to keyword search on timeout
- Database locks: Use advisory locks for resource contention (KB updates)
- Vector search: Tune IVFFlat parameters (nlist, probes) for your dataset size
- Network I/O: Use HTTP/2 multiplexing, enable compression for JSON responses
Deployment Architecture
A typical production deployment:
Load Balancer (Nginx)
|
+-- Python API (8 instances, port 5000)
| - User auth, model serving, LLM proxy
|
+-- Go Backend (8 instances, port 8080)
| - Search, chat, embeddings
|
+-- Database (PostgreSQL 14+)
| - pgvector extension for similarity search
|
+-- Vector Cache (Redis optional, in-process LRU default)
|
+-- Message Queue (Redis, optional, for async tasks)
Each service is independently scalable. Add more Go instances for search throughput; add more Python instances for LLM call capacity.