GoWind CMS integrates a complete observability system, including Jaeger distributed tracing, Redis cache monitoring, database slow query analysis, and application performance metric collection. This tutorial explains the performance monitoring solution under the CMS three-service architecture.
server:
tracer:
enabled: true
provider: jaeger
endpoint: "http://jaeger:14268/api/traces"
sampler:
type: const
param: 1
func (s *PostService) Create(ctx context.Context, req *contentV1.CreatePostRequest) (*contentV1.Post, error) {
ctx, span := tracer.Start(ctx, "PostService.Create")
defer span.End()
span.SetAttributes(
attribute.String("post.title", req.Data.GetTitle()),
attribute.Int("post.author_id", int(req.Data.GetCreatedBy())),
)
post, err := s.postRepo.Create(ctx, req)
if err != nil {
span.RecordError(err)
return nil, err
}
span.SetAttributes(attribute.Int("post.id", int(post.GetId())))
return post, nil
}
Visit http://localhost:16686:
| Feature | Description |
|---|
| Find Traces | Search traces by service/time/tags |
| Compare | Compare two traces |
| Dependencies | Service dependency graph |
| Service Map | Service topology |
func (r *PostRepo) List(ctx context.Context, req *paginationV1.PagingRequest) (*contentV1.ListPostResponse, error) {
ctx, span := tracer.Start(ctx, "PostRepo.List")
defer span.End()
start := time.Now()
result, err := r.query(ctx, req)
duration := time.Since(start)
span.SetAttributes(
attribute.Int64("db.duration_ms", duration.Milliseconds()),
attribute.Int("result.count", len(result.Items)),
)
if duration > 200*time.Millisecond {
span.SetAttributes(attribute.Bool("db.slow_query", true))
log.Warnf("慢查询: PostRepo.List 耗时 %v", duration)
}
return result, err
}
type Cache struct {
client *redis.Client
log *log.Helper
}
func (c *Cache) GetOrSet(ctx context.Context, key string, ttl time.Duration, fn func() (interface{}, error)) (interface{}, error) {
val, err := c.client.Get(ctx, key).Result()
if err == nil {
c.log.Debugf("缓存命中: %s", key)
return val, nil
}
result, err := fn()
if err != nil {
return nil, err
}
data, _ := json.Marshal(result)
c.client.Set(ctx, key, data, ttl)
c.log.Debugf("缓存写入: %s, TTL=%v", key, ttl)
return result, nil
}
func (s *PostService) Get(ctx context.Context, req *contentV1.GetPostRequest) (*contentV1.Post, error) {
cacheKey := fmt.Sprintf("post:detail:%d:%s", req.GetId(), req.GetLocale())
return s.cache.GetOrSet(ctx, cacheKey, 10*time.Minute, func() (*contentV1.Post, error) {
return s.postRepo.Get(ctx, req)
})
}
Related caches are automatically cleared when content is updated:
func (s *PostService) Update(ctx context.Context, req *contentV1.UpdatePostRequest) (*contentV1.Post, error) {
post, err := s.postRepo.Update(ctx, req)
if err != nil {
return nil, err
}
s.cache.Del(ctx, fmt.Sprintf("post:detail:%d", post.GetId()))
s.cache.DelPattern(ctx, "post:list:*")
return post, nil
}
log_min_duration_statement = 200
log_line_prefix = '%t [%p] %u@%d '
CREATE INDEX idx_posts_status_published_at ON posts (status, published_at DESC);
CREATE INDEX idx_posts_site_id ON posts (site_id);
CREATE INDEX idx_posts_slug ON posts (slug);
CREATE INDEX idx_posts_author_id ON posts (created_by);
CREATE INDEX idx_comments_post_id_status ON comments (post_id, status);
CREATE INDEX idx_comments_parent_id ON comments (parent_id);
CREATE INDEX idx_post_translations_post_lang ON post_translations (post_id, language_code);
data:
database:
driver: postgres
source: "host=postgres port=5432 ..."
max_open_conns: 25
max_idle_conns: 10
conn_max_lifetime: 300s
conn_max_idle_time: 60s
| Metric | Description | Alert Threshold |
|---|
| HTTP QPS | Requests per second | > 5000/s |
| HTTP P99 latency | 99th percentile response time | > 500ms |
| gRPC latency | Core Service call duration | > 200ms |
| Database queries | Slow query ratio | > 5% |
| Cache hit rate | Redis cache efficiency | < 80% |
| Error rate | HTTP 5xx ratio | > 1% |
| Goroutines | Active goroutine count | > 10000 |
var (
httpRequestDuration = prometheus.NewHistogramVec(
prometheus.HistogramOpts{
Name: "http_request_duration_seconds",
Help: "HTTP request duration",
},
[]string{"method", "path", "status"},
)
grpcRequestDuration = prometheus.NewHistogramVec(
prometheus.HistogramOpts{
Name: "grpc_request_duration_seconds",
Help: "gRPC request duration",
},
[]string{"service", "method"},
)
cacheHitRate = prometheus.NewGaugeVec(
prometheus.GaugeOpts{
Name: "cache_hit_rate",
Help: "Cache hit rate",
},
[]string{"cache_type"},
)
)
wrk -t4 -c100 -d30s http://localhost:6700/app/v1/posts
wrk -t4 -c100 -d30s -H "Authorization: Bearer xxx" http://localhost:6600/admin/v1/posts
echo "GET http://localhost:6700/app/v1/posts" | vegeta attack -duration=60s -rate=100 | vegeta report
vegeta report -type=json results.bin | jq
| Optimization | Method | Expected Gain |
|---|
| Database indexes | Analyze slow queries and add indexes | 50-90% |
| Redis cache | Cache hot data | 70-95% |
| Connection pool | Reasonable configuration | 20% |
| N+1 queries | Ent eager loading | 50-80% |
| Pagination | Cursor-based pagination | Significant with large data volumes |
| CDN static assets | Images/JS/CSS | 80% on the frontend |
| Gzip compression | Compress HTTP responses | 70% of bandwidth |
| Check Item | Description |
|---|
| Jaeger integration | Distributed tracing configuration |
| Redis cache | Hot data caching |
| Database indexes | Slow query analysis |
| Connection pool configuration | Reasonable connection counts |
| Performance metrics | Prometheus collection |
| Load testing | wrk / Vegeta |
| Slow query monitoring | PostgreSQL logs |