Skip to main content

Queue monitoring with Bull Board

Nanahoshi exposes a Bull Board dashboard at /admin/queues/ for real-time queue monitoring.

Accessing Bull Board

The dashboard is protected by admin authentication:
// apps/server/src/index.ts:46-54
app.use("/admin/*", async (c, next) => {
  const session = await auth.api.getSession({
    headers: c.req.raw.headers,
  });
  if (!session?.user || session.user.role !== "admin") {
    return c.text("Unauthorized", 401);
  }
  await next();
});
1

Login as admin

Ensure you’re logged in with an admin account
2

Navigate to Bull Board

Visit http://your-server:3000/admin/queues/
3

Monitor queues

View active jobs, completed, failed, and delayed jobs across all queues

Monitored queues

Bull Board tracks three BullMQ queues backed by Redis:

book-index

Full reindex jobs for Elasticsearch synchronization

file-events

File add/delete events from library scanning

cover-color

Cover color extraction for UI theming
// apps/server/src/index.ts:36-42
createBullBoard({
  queues: [
    new BullMQAdapter(bookIndexQueue),
    new BullMQAdapter(coverColorQueue),
    new BullMQAdapter(fileEventQueue),
  ],
  serverAdapter,
});

Docker logs

View all container logs

bun run infra:logs
This runs:
docker compose -f docker-compose.dev.yml --env-file apps/server/.env logs -f

View specific container logs

docker logs -f nanahoshi-v2-server

Filter logs by pattern

# Show only worker logs
docker logs nanahoshi-v2-server 2>&1 | grep "\[Worker\]"

# Show Elasticsearch indexing errors
docker logs nanahoshi-v2-server 2>&1 | grep "\[ES\]"

Elasticsearch monitoring

Cluster health

Check Elasticsearch status:
curl http://localhost:9200/_cluster/health?pretty
Expected response:
{
  "cluster_name": "docker-cluster",
  "status": "yellow",
  "number_of_nodes": 1,
  "active_primary_shards": 1,
  "active_shards": 1
}
Single-node clusters show yellow status because replicas cannot be allocated. This is normal for development.

Index stats

# Check book index size
curl http://localhost:9200/nanahoshi_books/_stats?pretty

# Count documents
curl http://localhost:9200/nanahoshi_books/_count?pretty

Kibana dashboard

Access Kibana at http://localhost:5602 (development environment only):
# docker-compose.dev.yml:50-59
kibana:
  image: docker.elastic.co/kibana/kibana:8.17.10
  environment:
    - ELASTICSEARCH_HOSTS=http://elasticsearch:9200
  ports:
    - "5602:5601"
Kibana is not included in production docker-compose.yml to reduce resource usage. Add it manually if needed.

Health checks

PostgreSQL

Health check runs every 5 seconds:
# docker-compose.yml:71-75
healthcheck:
  test: ["CMD-SHELL", "pg_isready -U postgres"]
  interval: 5s
  timeout: 5s
  retries: 5
Manual check:
docker exec nanahoshi-v2-postgres pg_isready -U postgres

Redis

Health check with authentication:
# docker-compose.yml:82-86
healthcheck:
  test: ["CMD", "redis-cli", "-a", "${REDIS_PASSWORD}", "ping"]
  interval: 5s
  timeout: 3s
  retries: 5
Manual check:
docker exec nanahoshi-v2-redis redis-cli -a YOUR_PASSWORD ping

Server endpoint

The root endpoint returns OK for basic health checks:
// apps/server/src/index.ts:247-249
app.get("/", (c) => {
  return c.text("OK");
});
Test:
curl http://localhost:3000/

Worker monitoring

File event worker

Processes file add/delete events with auto-scaled concurrency:
// packages/api/src/infrastructure/workers/file.event.worker.ts:85-94
const numCPUs = os.cpus().length;
const CONCURRENCY = Number(process.env.WORKER_CONCURRENCY) || Math.max(2, numCPUs * 2);
const LIMITER_MAX = Math.min(CONCURRENCY * 2, 100);
const LIMITER_DURATION = 1000;

console.log(`[Worker] Starting with concurrency=${CONCURRENCY} (CPUs=${numCPUs})`);
Progress logs every 1000 jobs:
// packages/api/src/infrastructure/workers/file.event.worker.ts:179-184
fileEventWorker.on("completed", (_job: Job) => {
  processedCount++;
  if (processedCount % 1000 === 0) {
    console.log(`[Worker] Completed ${processedCount} jobs`);
  }
});

Book index worker

Monitors reindexing progress:
// packages/api/src/infrastructure/workers/book.index.worker.ts:105-106
console.log(`[Worker] Indexed ${processedCount} books (lastId=${lastId})`);
await job.updateProgress(processedCount);
View progress in Bull Board or check logs:
docker logs nanahoshi-v2-server 2>&1 | grep "Indexed"

Resource usage

Check container resources

docker stats

Elasticsearch heap size

Configured in docker-compose.yml:
environment:
  - ES_JAVA_OPTS=-Xms512m -Xmx1g
Adjust based on your index size:
environment:
  - ES_JAVA_OPTS=-Xms1g -Xmx2g  # For larger libraries

Build docs developers (and LLMs) love