SSD Nodes Learn Hosting plans →
নির্দেশিকা Matt Connorদ্বারা Matt Connor

নিজের VPS-এ Langfuse চালিয়ে AI agent trace করুন

নিজের VPS-এ Langfuse চালানোর বাস্তব resource floor, pinned image tag, TLS, disk ভরার আগে ClickHouse retention এবং কার্যকর backup সেটআপ জানুন।

একটি AI agent trace করবেন কেন

আপনার agent আসলে কী করেছে তা একটি run-এ দেখার জন্য আপনি নিজের ব্যবস্থাপনায় Langfuse চালান। Langfuse একটি open source LLM (large language model) observability tool। এটি প্রতিটি prompt, প্রতিটি model response, প্রতিটি tool call এবং প্রতিটি token রেকর্ড করে। এরপর এগুলোকে একটি trace-এর অধীনে group করে, যা আপনি খুলে পড়তে পারেন। নিজের VPS-এ এটি চালালে ওই prompt-গুলো আপনার নিয়ন্ত্রণাধীন server-এর বাইরে যায় না।

এটি করার কারণ সরল। যে cost সমস্যা বা quality সমস্যা আপনি দেখতে পান না, তা ঠিক করতে পারবেন না। Provider invoice আপনাকে জানায় যে Tuesday-এর খরচ Monday-এর খরচের চার গুণ ছিল। একটি trace জানায় কোন agent run এটি ঘটিয়েছে, কোন prompt 40,000 token-এ বড় হয়েছে, এবং ব্যর্থ হওয়ার আগে কোন retry loop নয় বার চলেছে। Invoice শুধু সংখ্যা দেয়। Trace সেই code দেখায় যা ওই ফল তৈরি করেছে।

এই guide-এ তিনটি term ব্যবহার করা হয়েছে। একটি trace হলো আপনার agent-এর একটি end-to-end run। একটি observation হলো ওই run-এর ভেতরের একটি ধাপ: সাধারণ code-এর জন্য একটি span এবং model-এ call-এর জন্য একটি generation। একটি score হলো trace-এর সঙ্গে যুক্ত একটি সংখ্যা, যা human review অথবা automated evaluator থেকে আসে। Langfuse OpenTelemetry (OTel) ব্যবহার করে। এটি distributed tracing-এর vendor-neutral standard। তাই আপনার আগে থেকেই থাকা instrumentation Langfuse-এ নির্দেশ করতে পারে।

Self-hosting-এ Langfuse আসলে কী চালায়

Langfuse v4 একটি container নয়। এটি দুটি application container এবং চারটি storage service নিয়ে গঠিত। একটি VPS-এ এই ছয়টিই আপনার server-এ চলে।

  • langfuse-web web interface এবং ingestion API সরবরাহ করে।
  • langfuse-worker background-এ queue খালি করে। এটি ingestion batch parse করে, cost হিসাব করে এবং nightly retention job চালায়।
  • Postgres users, organisations, projects, API keys এবং prompts-এর মতো transactional data সংরক্ষণ করে।
  • ClickHouse trace data নিজে সংরক্ষণ করে, অর্থাৎ observations এবং scores। এটি analytical query-এর জন্য তৈরি একটি column store। তাই একশো মিলিয়নের বেশি row-এর dashboard-ও দ্রুত উত্তর দেয়।
  • Redis web এবং worker-এর মাঝখানে থাকা queue এবং cache হিসেবে কাজ করে।
  • MinIO server-এ S3 compatible object storage সরবরাহ করে। এটি প্রতিটি raw incoming event এবং আপনি সংযুক্ত করা যেকোনো media সংরক্ষণ করে।

Langfuse চালানো প্রতিটি component-এর জন্য minimum resource প্রকাশ করে। Chart-এ কাজ সম্পাদনকারী তিনটি component দেখানো হয়েছে।

চার্টLangfuse published minimum resources per component
এই চার্টের পেছনের ডেটা
[
  {
    "label": "ClickHouse",
    "cpu_cores": 2,
    "memory_gib": 8
  },
  {
    "label": "Langfuse web",
    "cpu_cores": 2,
    "memory_gib": 4
  },
  {
    "label": "Langfuse worker",
    "cpu_cores": 2,
    "memory_gib": 4
  }
]

শুধু ClickHouse-এর জন্যই 8 GiB memory প্রয়োজন। Web container এবং worker-এর প্রত্যেকটির জন্য 4 GiB করে প্রয়োজন। Chart-এ থাকা 3টি component-এর জন্য এগুলো প্রকাশিত minimum resource। একই page-এ Postgres-এর জন্য 4 GiB, Redis বা Valkey-এর জন্য 1.5 GiB এবং MinIO-এর জন্য 4 GiB উল্লেখ আছে। তাই ছয়টি component-এর minimum resource মিলিয়ে মোট 25.5 GiB হয়। প্রকল্পটির নিজস্ব Docker Compose guide একটি single machine-এর জন্য অন্তত 4 core, 16 GiB memory এবং প্রায় 100 GiB storage সুপারিশ করে। এটি component-গুলোর minimum resource-এর যোগফলের চেয়ে কম। তাই 16 GiB-কে shared Compose server-এর জন্য সর্বনিম্ন প্রয়োজন হিসেবে ধরুন, অতিরিক্ত capacity হিসেবে নয়।

2 GiB plan-এ এটি চালানোর চেষ্টা করবেন না। ClickHouse শুরু হয়, কিছু সময় write গ্রহণ করে, তারপর background merge চলাকালে বন্ধ হয়ে যায়। কারণ merge table-এর বড় অংশ memory-তে load করে। আপনি docker compose ps-এ clickhouse container-এর অবস্থা restarting দেখতে পাবেন, dmesg-এ Out of memory: Killed process 1234 (clickhouse-serv)-এর মতো একটি line থাকবে এবং প্রতিটি Langfuse dashboard 500 response দেবে। চাপ কম থাকলে ClickHouse query প্রত্যাখ্যান করে এবং পরিবর্তে DB::Exception: Memory limit (total) exceeded log করে। পরিকল্পনার জন্য 16 GiB-ই ধরুন। একই VPS-এ অন্য কিছু চালানোর কথা থাকলে তার জন্য আলাদা resource বরাদ্দ করুন। কারণ নিজস্বভাবে চালানো AFFiNE workspace-এর মতো তুলনামূলকভাবে হালকা stack-এরও নিজস্ব কয়েক GiB memory প্রয়োজন, এবং ClickHouse তার কোনো অংশ ছেড়ে দেবে না।

Docker Compose দিয়ে Langfuse deploy করুন

রিপোজিটরিটি clone করুন। Stack, wiring এবং default environment সবই এর docker-compose.yml-এ আছে।

git clone https://github.com/langfuse/langfuse.git
cd langfuse

এই ফাইলে যেসব value পরিবর্তন করতে হবে, সেগুলো # CHANGEME দিয়ে চিহ্নিত করা আছে। প্রথমে তিনটি application secret তৈরি করুন।

openssl rand -base64 32   # NEXTAUTH_SECRET
openssl rand -base64 32   # SALT
openssl rand -hex 32      # ENCRYPTION_KEY

ENCRYPTION_KEY-এর দৈর্ঘ্য 256 bits হতে হবে এবং এটি 64টি hexadecimal character হিসেবে লিখতে হবে। openssl rand -hex 32 ঠিক এই format-ই print করে। এটি at-rest অবস্থায় থাকা sensitive value encrypt করে, যার মধ্যে instance-এ সংরক্ষণ করা LLM provider key-ও রয়েছে। Data তৈরি হওয়ার পরে এটি পরিবর্তন করলে সেই row-গুলো আর decrypt করা যাবে না। তাই প্রথম boot থেকেই এটিকে স্থায়ী value হিসেবে বিবেচনা করুন। SALT আপনার Langfuse API key hash করতে ব্যবহৃত হয়। এটি পরিবর্তন করলে agent-গুলো ইতিমধ্যে যে key ব্যবহার করছে, সেগুলো সব invalid হয়ে যাবে।

এরপর POSTGRES_PASSWORD, CLICKHOUSE_PASSWORD, REDIS_AUTH এবং MINIO_ROOT_PASSWORD সেট করুন। MinIO password চার জায়গায় থাকে: একবার MINIO_ROOT_PASSWORD হিসেবে, তারপর LANGFUSE_S3_EVENT_UPLOAD_SECRET_ACCESS_KEY, LANGFUSE_S3_MEDIA_UPLOAD_SECRET_ACCESS_KEY এবং LANGFUSE_S3_BATCH_EXPORT_SECRET_ACCESS_KEY হিসেবে। কোনো একটি বাদ পড়লে MinIO সেই client-কে SignatureDoesNotMatch দিয়ে reject করে। এই error worker log-এ দেখা যায়, যদিও web interface তখনও স্বাভাবিক দেখাতে পারে। Tracked compose file-এ value না রেখে env file-এ রাখা হলো সেই pattern, যা Docker Compose env file এবং secret-এ দেখানো হয়েছে।

শুরু করার আগে image tag নির্দিষ্ট করুন

সরবরাহ করা ফাইলে docker.langfuse.com/langfuse/langfuse:4 এবং docker.langfuse.com/langfuse/langfuse-worker:4 ব্যবহার করা হয়েছে। এই tag পরিবর্তিত হয়। Langfuse start হওয়ার সময় তার Postgres এবং ClickHouse migration স্বয়ংক্রিয়ভাবে চালায়। তাই কয়েক মাস পরে করা সাধারণ docker compose pull এমন একটি database-এ অনিয়োজিত schema migration চালাতে পারে, যেটির সেই সকালে backup নেওয়া হয়নি। একটি docker-compose.override.yml-এ দুটিকেই একই release-এ pin করুন। Compose এটি সরবরাহ করা ফাইলের ওপর merge করে, ফলে পরবর্তী git pull আপনার edit-এর সঙ্গে সংঘর্ষ তৈরি করবে না। একই image Docker Hub-এও প্রকাশিত হয়। নিচের override সেখান থেকেই image pull করে।

services:
  langfuse-web:
    image: docker.io/langfuse/langfuse:4.72.0
  langfuse-worker:
    image: docker.io/langfuse/langfuse-worker:4.72.0

August 2026 অনুযায়ী Version 4.3.1 ছিল বর্তমান 4.3 release। এরপর release দ্রুত এসেছে, এবং October 11, 2026 অনুযায়ী GitHub-এ v4.56.0 ছিল সর্বশেষ। October 31, 2026 অনুযায়ী GitHub-এ সর্বশেষ release ছিল v4.72.0; উপরের override-এ সেটিই ব্যবহার করা হয়েছে। প্রকল্পের GitHub releases page পরীক্ষা করুন। Deploy করার দিন যে version বর্তমান, সেটি pin করুন। এরপর version number ইচ্ছাকৃতভাবে পরিবর্তন করুন। সরবরাহ করা ফাইলের কিছু storage image-এর ক্ষেত্রেও একই কাজ করা উচিত, কারণ তাদের tag-ও পরিবর্তিত হয়। postgres:17 (POSTGRES_VERSION variable-এর default) এবং redis:7 major version অনুসরণ করে। সরবরাহ করা ফাইলে এখন docker.io/clickhouse/clickhouse-server-এর জন্য exact version tag ব্যবহার করা হয়েছে। এতে exact ClickHouse version pin করা হয়। এই নিয়ম শুধু Langfuse-এর জন্য নয়: একটি self-hosted openGym workout tracker এই stack-এর সামান্য অংশ চালায়, তবুও একটি নির্দিষ্ট git tag ব্যবহার করা উচিত। কারণ start হওয়ার সময় নিজস্ব database migration চালায় এমন যেকোনো software-এর ক্ষেত্রে সাধারণ pull schema change ঘটাতে পারে।

এটি চালু করুন।

docker compose up -d
docker compose ps
docker compose logs -f langfuse-worker

প্রথম boot-এ migration চলে। তাই কোনো response পাওয়ার আগে দুই বা তিন মিনিট অপেক্ষা করুন। Web container চালু হলে Ready log করে। docker compose ps-এ running state-এ ছয়টি service থাকার কথা। কোনো container বারবার restart হলে তার log-এ কারণটি থাকবে। Web container start হওয়ার সময় ClickHouse migration প্রয়োগ করা হয়। তাই ভুল CLICKHOUSE_MIGRATION_URL-এর error আপনি docker compose logs langfuse-web-এ দেখবেন, যে worker log monitor করছেন সেখানে নয়। এই variable port 9000-এ ClickHouse native protocol ব্যবহার করে, HTTP port 8123 নয়। এটিকে 8123-এ point করলে web container-এর startup migration ব্যর্থ হবে। Worker ধীরগতির background migration চালায়। তাই সেই migration-এর report worker log-এ দেখা যায়।

সার্ভার থেকেই health পরীক্ষা করুন।

curl -s "http://localhost:3000/api/public/health?failIfDatabaseUnavailable=true"
curl -s -o /dev/null -w '%{http_code}\n' http://localhost:3000/api/public/ready

সাধারণ /api/public/health call শুধু প্রমাণ করে যে API process চালু আছে। কারণ default অবস্থায় এটি database পরীক্ষা করে না। failIfDatabaseUnavailable=true form-টি monitor-এর জন্য ব্যবহার করা উচিত। Database unreachable হলে এটি 503 return করে। Web container traffic গ্রহণের জন্য ready হলে /api/public/ready 200 return করে। Shutdown-এর নির্দেশ পাওয়ার পরে এটি 500 return করে। দুটিই সাধারণ HTTP check। তাই একটি Uptime Kuma status page এগুলো monitor করতে পারে এবং agent-গুলো টের পাওয়ার আগেই জানাতে পারে যে stack down।

TLS সামনে রেখে অতিরিক্ত port বন্ধ করুন

প্রদত্ত Compose ফাইলে web container-এর জন্য 3000:3000 এবং MinIO-এর জন্য 9090:9000 প্রকাশিত থাকে। উভয়ই প্রতিটি interface-এ bind করে। Public IP-তে এর অর্থ হলো, যে কেউ port 3000 scan করলে আপনার sign-up page-এ পৌঁছাতে পারে, আর যে কেউ 9090 scan করলে আপনার raw prompt রাখা bucket-এর সঙ্গে সংযোগ করে।

শুধু firewall rule দিয়ে এগুলো বন্ধ হয় না। Docker nat table-এ নিজস্ব DNAT rule লেখে। প্যাকেট ufw-এর filter rule-এ পৌঁছানোর আগেই সেগুলো মূল্যায়ন করা হয়। তাই ufw deny 3000 দিলেও published port খোলা থাকে। এই সমস্যা এত সাধারণ যে এর জন্য আলাদা guide আছে: Docker-এর published port কেন ufw এড়িয়ে যায়। পরিবর্তে আপনার override file-এ loopback-এ bind করুন। Docker Compose 2.24.4 বা পরবর্তী সংস্করণে published port প্রতিস্থাপন করতে !override ব্যবহার করুন। সাধারণ list দিলে মূল binding-এর সঙ্গে merge হয়ে যায়।

services:
  langfuse-web:
    ports: !override
      - "127.0.0.1:3000:3000"
    environment:
      NEXTAUTH_URL: https://langfuse.example.com
  minio:
    ports: !override
      - "127.0.0.1:9090:9000"
      - "127.0.0.1:9091:9001"

NEXTAUTH_URL-এ scheme-সহ সঠিক public address দিতে হবে। Login flow এই মান থেকে callback URL তৈরি করে। HTTPS proxy-এর পেছনে এটি http://localhost:3000 রাখলে sign-in round trip browser-কে এমন ঠিকানায় পাঠাবে যেখানে সেটি পৌঁছাতে পারবে না।

এখন একটি reverse proxy-কে 127.0.0.1:3000-এর দিকে নির্দেশ করুন এবং certificate সেটির মাধ্যমে পরিচালনা করুন। একই Compose project-এ Traefik ব্যবহার করলে একটি Traefik reverse proxy-এর পেছনে একাধিক অ্যাপ চালানো-এ বর্ণিত label ব্যবহার করে Compose network-এর মাধ্যমে langfuse-web:3000-এ route করুন। Langfuse-ই যদি মেশিনের একমাত্র অ্যাপ হয়, Caddy দুই লাইনে একই কাজ করতে পারে। curl -sI https://langfuse.example.com/api/public/ready দিয়ে যাচাই করুন। এরপর দ্বিতীয় একটি মেশিন থেকে নিশ্চিত করুন যে curl http://YOUR_IP:3000 সংযোগ করতে পারে না।

MinIO নিয়ে একটি সতর্কতা আছে। Langfuse সেই S3 endpoint-এর দিকে নির্দেশ করা presigned URL তৈরি করে SDK-এর মাধ্যমে media upload এবং browser-এ তা load—উভয় কাজের জন্য। তাই image বা audio-সহ multi-modal trace ব্যবহার করলে loopback-only MinIO-এর কারণে অন্য মেশিন থেকে attachment upload হবে না এবং browser-এও load হবে না। এটি proxy করার আগে blob storage configuration page পড়ুন। কারণ presigned URL-এ লেখা endpoint, LANGFUSE_S3_MEDIA_UPLOAD_ENDPOINT, আপনি যে endpoint প্রকাশ করছেন তার সঙ্গে মিলতে হবে। Plain-text trace এতে প্রভাবিত হবে না।

প্রথমবার visit করার সময় আপনার account তৈরি করুন। এরপর instance-টির নিয়ন্ত্রণ নিজের কাছে রাখুন। আপনার account তৈরি হয়ে গেলে AUTH_DISABLE_SIGNUP=true সেট করুন, যাতে page-এ পৌঁছানো কোনো অপরিচিত ব্যক্তি আর register করতে না পারে। Langfuse-এ LANGFUSE_ALLOWED_ORGANIZATION_CREATORS-ও আছে। এটি কারা organisation তৈরি করতে পারবে তা সীমিত করে। তবে এটি Enterprise Edition-এর একটি feature এবং license key থাকলেই কার্যকর হয়। আপনি যদি ইতিমধ্যে নিজের identity provider হিসেবে Authentik চালান, Langfuse একটি standard OIDC connection গ্রহণ করে। ফলে account-গুলো শুধু এই box-এর জানা password list-এ না থেকে আপনার অন্যান্য app-এর সঙ্গে একসঙ্গে তৈরি ও অপসারিত হয়।

আপনার প্রথম trace পাঠান

Web interface-এ একটি project তৈরি করুন এবং project settings থেকে এর public key ও secret key কপি করুন। Python SDK তিনটি environment variable পড়ে।

export LANGFUSE_PUBLIC_KEY="pk-lf-..."
export LANGFUSE_SECRET_KEY="sk-lf-..."
export LANGFUSE_BASE_URL="https://langfuse.example.com"

SDK v4-এ LANGFUSE_BASE_URL হলো variable-এর নাম। এই version-টি March 2026-এ release হয়েছে। পুরোনো code এবং পুরোনো guide-এ LANGFUSE_HOST ব্যবহার করা হয়। আপনার trace যদি server-এর পরিবর্তে Langfuse Cloud-এ পৌঁছায়, তার কারণ হলো base URL unset। কারণ default URL hosted instance-কে নির্দেশ করে।

বর্তমান Python SDK v5-এর জন্য Python 3.11 বা পরবর্তী version এবং self-hosted Langfuse v4.59.0 বা পরবর্তী version প্রয়োজন। উপরে pinned server এই requirement পূরণ করে।

pip install langfuse opentelemetry-instrumentation-anthropic anthropic
import os
from anthropic import Anthropic
from langfuse import get_client, observe
from opentelemetry.instrumentation.anthropic import AnthropicInstrumentor

AnthropicInstrumentor().instrument()
langfuse = get_client()
client = Anthropic(api_key=os.environ["ANTHROPIC_API_KEY"])

@observe(as_type="tool")
def lookup_order(order_id: str) -> str:
    return f"order {order_id}: shipped"

@observe()
def handle_request(question: str) -> str:
    context = lookup_order("A-1042")
    message = client.messages.create(
        model="claude-haiku-4-5",
        max_tokens=512,
        messages=[{"role": "user", "content": f"{context}\n\n{question}"}],
    )
    return message.content[0].text

if __name__ == "__main__":
    assert langfuse.auth_check()
    print(handle_request("Where is my order?"))
    langfuse.flush()

@observe decorator function-এর চারপাশে একটি observation খোলে, function-এর arguments ও return value capture করে এবং ইতিমধ্যে active থাকা observation-এর অধীনে সেটিকে nest করে। AnthropicInstrumentor হলো Anthropic client-এর জন্য OpenTelemetry instrumentation। এটি প্রতিটি messages.create call-কে একটি generation-এ রূপান্তর করে। এতে model name, token usage এবং latency থাকে। Call site-এ কোনো পরিবর্তন প্রয়োজন হয় না।

দুটি call আপনার হয়ে পরীক্ষা সম্পন্ন করে। ভুল key বা ভুল base URL থাকলে langfuse.auth_check() False ফেরত দেয়। Dashboard খালি থাকার কারণ অনুমান করার চেয়ে এটি দ্রুত ফল দেয়। langfuse.flush() queue-তে থাকা span পাঠানো পর্যন্ত অপেক্ষা করে। স্বল্পস্থায়ী process-এর জন্য এটি প্রয়োজন, কারণ SDK background-এ batch তৈরি করে। কোনো script সঙ্গে সঙ্গে exit করলে তার unsent batch-ও সঙ্গে চলে যায়। একটি script-এর পরিবর্তে পুরো team trace পাঠালে, প্রতিটি ব্যক্তির shell-এ না দিয়ে gateway-তে একবার এই তিনটি variable set করুন, যেটির মধ্য দিয়ে সবার agent ইতিমধ্যে চলে। এভাবেই একটি self-hosted OneCLI harness প্রতিটি সহকর্মীর run instrumented রাখে এবং key এক জায়গায় সংরক্ষিত থাকে।

ClickHouse কেন ক্রমাগত বড় হতে থাকে?

বেশিরভাগ মানুষ self-host করে এমন ডেটার মধ্যে trace-ই সবচেয়ে দ্রুত বাড়ে। প্রতিটি agent run-এর প্রতিটি ধাপের জন্য একটি row লেখা হয়। Input ও output সম্পূর্ণভাবে সংরক্ষণ করা হয়। তাই দীর্ঘ prompt-সহ বেশি কথোপকথনকারী agent প্রতিদিন তার পর্যবেক্ষণাধীন application-এর চেয়ে অনেক বেশি byte তৈরি করে। এভাবে চলতে দিলে ClickHouse disk পূর্ণ করে ফেলে। Disk পূর্ণ হলে ingestion ধীর হয় না; বরং বন্ধ হয়ে যায়।

এখানে দুটি পৃথক বিষয় বাড়ে। তাই সমাধানও দুটি পৃথক।

প্রথমটি হলো আপনার নিজস্ব trace data। এর সমাধান হলো retention setting। self-hosted instance-এ data retention একটি Enterprise Edition feature এবং এর জন্য license key প্রয়োজন। License key না থাকলে Langfuse trace, observation, score এবং media অনির্দিষ্টকাল ধরে রাখে। তাই সেই অনুযায়ী disk-এর আকার নির্ধারণ করুন এবং এই section-এর শেষে থাকা disk alert-এর ওপর নির্ভর করুন। License থাকলে project owner এবং admin-রা project settings খুলে দিনের হিসাবে data retention period নির্ধারণ করতে পারেন। Langfuse ন্যূনতম 3 দিন গ্রহণ করে। এরপর একটি nightly job নির্ধারিত সময়সীমার চেয়ে পুরোনো trace, observation, score এবং media asset নির্বাচন করে ClickHouse ও blob storage থেকে মুছে ফেলে। তবে saved dataset item-এর সঙ্গে সম্পর্কিত media এর ব্যতিক্রম। Bucket-এ jobটির DeleteObject permission প্রয়োজন। Default compose file-এ থাকা MinIO root credential-এ এই permission আগে থেকেই থাকে। Deletion স্থায়ী। তাই দীর্ঘমেয়াদি history প্রয়োজন হলে আগে blob storage export configure করুন। Langfuse-এর নিজস্ব table-এ হাতে TTL clause লিখবেন না। Retention job-ই ClickHouse এবং bucket-কে সমন্বয়ে রাখে। Manual TTL শুধু একটি দিক মুছে ফেলে।

আপনি বাস্তবে যে সময়সীমা ব্যবহার করেন, সেটির ভিত্তিতে window নির্বাচন করুন। Cost ও quality review মাস কয়েক আগের data-তে নয়, কয়েক দিন আগের data-তে হয়। ছোট team-এর জন্য 30 দিন একটি যুক্তিসঙ্গত শুরু। কোনো কিছু নষ্ট হলে তবেই trace খুলে দেখলে 14 দিন যথেষ্ট।

দ্বিতীয়টি হলো ClickHouse-এর নিজস্ব system log table। retention configure করার পরেও disk বাড়তে থাকায় এই বিষয়টি অনেককে অবাক করে। ClickHouse নিজস্ব diagnostic-এর জন্য trace_log, text_log, opentelemetry_span_log, metric_log, asynchronous_metric_log এবং latency_log লেখে। এগুলোর সঙ্গে কোনো TTL থাকে না। Langfuse কখনো এগুলো পড়ে না। তাই প্রথমে disk আসলে কোথায় ব্যবহৃত হয়েছে তা খুঁজে বের করুন।

SELECT table, formatReadableSize(size) AS size, rows FROM (
    SELECT table, database, sum(bytes) AS size, sum(rows) AS rows
    FROM system.parts
    WHERE active
    GROUP BY table, database
    ORDER BY size DESC
)

docker compose exec clickhouse clickhouse-client --password "$CLICKHOUSE_PASSWORD" দিয়ে এটি চালান। System table-গুলো যদি তালিকার উপরের দিকে থাকে, তবে config overlay ব্যবহার করে সেগুলো বন্ধ করুন। কারণ ClickHouse start হওয়ার সময় /etc/clickhouse-server/config.d/-এর প্রতিটি file main config-এর ওপর merge করে।

<clickhouse>
    <trace_log remove="1"/>
    <text_log remove="1"/>
    <opentelemetry_span_log remove="1"/>
    <asynchronous_metric_log remove="1"/>
    <metric_log remove="1"/>
    <latency_log remove="1"/>
</clickhouse>

এটি mount করুন এবং ClickHouse restart করুন।

services:
  clickhouse:
    volumes:
      - ./clickhouse-config.d/system-logs.xml:/etc/clickhouse-server/config.d/system-logs.xml:ro

এতে নতুন write বন্ধ হবে। Disk-এ থাকা row মুছে যাবে না। তাই DROP TABLE IF EXISTS system.trace_log ব্যবহার করে এবং সরিয়ে দেওয়া প্রতিটি table-এর জন্য একইভাবে space পুনরুদ্ধার করুন। Diagnostic রেখে দিতে চাইলে remove="1"-এর পরিবর্তে প্রতিটি table-এ aggressive TTL ব্যবহার করতে পারেন। Langfuse scaling docs-এ এর বিস্তারিত দেওয়া আছে। এই TTL directive শুধু system table প্রথমবার তৈরি হওয়ার সময় প্রযোজ্য হয়। তাই existing install-এর ক্ষেত্রে একই docs table-এ পরে TTL যোগ করার পদ্ধতিও দেখায়।

আরও একটি table সম্পর্কে জানা দরকার। blob_storage_file_log আপনার bucket-এ upload করা event file track করে। Bucket-এ lifecycle policy সেট করলেও table-এ matching TTL দিন, যাতে দুটির মধ্যে অসামঞ্জস্য তৈরি না হয়।

ALTER TABLE blob_storage_file_log MODIFY TTL created_at + INTERVAL 30 DAY DELETE;

Data disk-এর ওপর একটি সাধারণ df -h alert-ও যুক্ত করুন। Trace মসৃণভাবে বাড়ে না। নতুন agent ship করার দিনেই তা দ্রুত বাড়তে পারে। সেই পরিস্থিতির প্রথম লক্ষণ ingestion ব্যর্থ হওয়া উচিত নয়।

Postgres এবং ClickHouse-এর ব্যাকআপ নিন

Langfuse-এর ব্যাকআপের 3টি অংশ রয়েছে। Postgres-এ আপনার user, organisation, project এবং API key থাকে। ClickHouse-এ trace থাকে। MinIO-তে raw event থাকে। শুধু Postgres restore করলে history ছাড়া একটি কার্যকর login পাবেন। শুধু ClickHouse restore করলে এমন history পাবেন, যা দেখার জন্য কেউ login করতে পারবে না। এই বিভাজন শুধু Langfuse-এর ক্ষেত্রে নয়। self-hosted Chatwoot support desk-এর ক্ষেত্রেও একই কাঠামো দেখা যায়। সেখানে uploads directory ছাড়া নেওয়া Postgres dump restore করলে conversation ফিরে আসে, কিন্তু সব attachment হারিয়ে যায়।

Postgres একটি সাধারণ pg_dump, এবং Docker install-এর জন্য Langfuse-এর backup documentation-এ এটিই ব্যবহার করা হয়েছে।

docker compose exec -T postgres pg_dump -U postgres postgres \
  | gzip > langfuse-pg-$(date +%F).sql.gz

ClickHouse-এর ক্ষেত্রে আরও সতর্কতা দরকার, কারণ merge চলার সময় live data directory copy করলে সেটি consistent backup হয় না। একটি server-এ সহজ পদ্ধতি হলো container বন্ধ করে volume archive করা।

docker compose stop clickhouse
docker volume ls | grep clickhouse
docker run --rm -v langfuse_langfuse_clickhouse_data:/data -v "$PWD":/backup alpine \
  tar czf /backup/langfuse-ch-$(date +%F).tar.gz -C /data .
docker compose start clickhouse

docker volume ls যে volume name দেখায়, সেটি ব্যবহার করুন; YAML-এ লেখা নামটি নয়। File-এ langfuse_clickhouse_data ঘোষণা করা আছে, এবং Compose তার সঙ্গে project name যোগ করে। তাই langfuse নামের directory-তে clone করলে langfuse_langfuse_clickhouse_data তৈরি হয়। এটি ভুল হলে docker run কোনো সতর্কতা ছাড়াই একটি নতুন খালি volume তৈরি করে, এবং আপনার archive-এ কিছুই থাকে না।

Langfuse প্রতিটি ingestion request-এর সময় raw event data bucket-এ upload করে এবং ব্যর্থ ingestion job retry করার সময় সেটি আবার পড়ে। তাই ClickHouse অল্প সময় বন্ধ থাকলে সাধারণত worker পরে job retry করে। কাজটি কম traffic-এর সময় করুন এবং বন্ধ রাখার সময় সংক্ষিপ্ত রাখুন। বেশি ব্যস্ত instance-এর ক্ষেত্রে ClickHouse-এর নিজস্ব BACKUP DATABASE default TO S3(...) statement server বন্ধ না করেই একটি consistent backup লিখতে পারে। MinIO হলো তৃতীয় অংশ। mc mirror অথবা off-box bucket-এ MinIO replication ব্যবহার করলে সেটিও সুরক্ষিত থাকে। যাই তৈরি করুন না কেন, সেটি server-এর বাইরে সংরক্ষণ করুন। এ কারণেই VPS-এ encrypted restic backup ব্যবহার করা হয়।

Redis queue এবং cache ধরে রাখে। Redis হারালে bucket-এ raw event file থাকলেও queued job হারিয়ে যায়।

Consistency-এর এই সীমাবদ্ধতা বাস্তব এবং এটি স্পষ্টভাবে বলা দরকার। Postgres এবং ClickHouse ভিন্ন সময়ে dump করা হয়। তাই restore করার পরে কোনো project row-এর সঙ্গে কোনো trace নাও থাকতে পারে, অথবা এমন trace থাকতে পারে যা আর বিদ্যমান কোনো project-এর অন্তর্ভুক্ত নয়। Langfuse এটি সহ্য করতে পারে। তবে দুটি dump কাছাকাছি সময়ে এবং কম traffic-এর সময় নিন। Event bucket-ই প্রকৃত safety net, কারণ Langfuse প্রতিটি ingestion request-এর সময় সেখানে raw event data upload করে।

অন্তত একবার একটি scratch stack-এ restore করে পরীক্ষা করুন। এতে ভুল volume name সম্পর্কে এখনই জানা যায়, outage চলাকালীন নয়।

প্রথমে যা দেখবেন

প্রথম সপ্তাহে চারটি বিষয় অগ্রাধিকার পাওয়ার মতো।

  • প্রতি trace-এর খরচ। Langfuse model name এবং token usage থেকে খরচ হিসাব করে। তাই trace-গুলো খরচ অনুযায়ী সাজিয়ে সবচেয়ে ব্যয়বহুল trace শুরু থেকে শেষ পর্যন্ত পড়ুন। সাধারণত কারণটি হলো বড় হয়ে যাওয়া prompt: পুরো একটি document context-এ paste করা হয়েছে, অথবা এমন conversation history রাখা হয়েছে যা কেউ trim করে না। এটি দেখতে পারলে একটি AI agent আপনার কত খরচ করছে তা নিয়ন্ত্রণ করা অনুমানের বদলে engineering task হয়ে যায়।
  • Input এবং output অনুযায়ী token usage-এর বিভাজন। Input token বেশি এবং সস্তা, output token কম কিন্তু ব্যয়বহুল, আর cached input আরও সস্তা। একই হিসাব Claude Code token usage কীভাবে গণনা করা হয়-এ বিস্তারিতভাবে ব্যাখ্যা করা হয়েছে। আপনি নিজে লিখে তৈরি করা যেকোনো agent-এর ক্ষেত্রেও এটি প্রযোজ্য।
  • Latency percentile। Median সমস্যাটি আড়াল করে। Timeout সাধারণত p95 এবং p99-এ দেখা যায়। Agent loop-এর ভিতরে p95-এ ধীর একটি tool call-এর প্রভাব iteration-এর সংখ্যার সঙ্গে গুণিত হয়।
  • ব্যর্থ tool call। Level ERROR অনুযায়ী observation filter করুন। কোনো tool 5% সময় ব্যর্থ হলে aggregate success rate-এ তা চোখে পড়ে না। কিন্তু trace-এ তা স্পষ্ট দেখা যায়, যেখানে model পুনরায় চেষ্টা করে এবং পরে workaround করতে token খরচ করে।

Retention window নির্ধারণ করুন এবং deploy করার দিনই ঠিক করুন, প্রতি সপ্তাহে একই দিনে কোন dashboard পরীক্ষা করবেন। যে observability tool কেউ খোলে না, সেটি শেষ পর্যন্ত এমন একটি database হয়ে যায় যা disk পূর্ণ করে।

FAQ

একটি self-hosted Langfuse-এর কত memory প্রয়োজন?

4 CPU core এবং 16 GiB memory-এর পরিকল্পনা করুন। একটি virtual machine-এর জন্য Langfuse Docker Compose guide-এ এটিই সুপারিশ করা হয়েছে। এর সঙ্গে প্রায় 100 GiB storage রাখুন। প্রকাশিত component minimum অনুযায়ী ClickHouse-এর জন্য 8 GiB এবং web ও worker container-এর প্রতিটির জন্য 4 GiB প্রয়োজন। একই পৃষ্ঠায় Postgres-এর জন্য 4 GiB, Redis-এর জন্য 1.5 GiB এবং MinIO-এর জন্য 4 GiB উল্লেখ আছে। তাই ছয়টি component মিলে মোট 25.5 GiB হয়, যা 16 GiB Compose recommendation-এর চেয়ে বেশি।

Data retention সেট করার পরেও ClickHouse disk কেন পূর্ণ হতে থাকে?

Retention setting শুধু Langfuse-এর নিজস্ব data-এর ক্ষেত্রে প্রযোজ্য। ClickHouse আলাদাভাবে তার diagnostic table trace_log, text_log, opentelemetry_span_log, metric_log, asynchronous_metric_log এবং latency_log-এ data লেখে। এগুলোতে কোনো TTL থাকে না। কোন table সবচেয়ে বড় তা দেখতে table অনুযায়ী group করে system.parts query চালান। এরপর /etc/clickhouse-server/config.d/-এর অধীনে থাকা কোনো file-এ remove="1" entry ব্যবহার করে অব্যবহৃত table-গুলো disable করুন, ClickHouse restart করুন এবং আগে ব্যবহৃত space পুনরুদ্ধার করতে বিদ্যমান table-গুলো drop করুন।

Langfuse-এ minimum data retention period কত?

তিন দিন। Project settings-এ প্রতি project-এর জন্য retention নির্ধারণ করা যায়। এটি Organization Management API দিয়েও নির্ধারণ করা যায়। একটি nightly job window-এর চেয়ে পুরোনো trace, observation, score এবং media asset ClickHouse ও blob storage—উভয় স্থান থেকেই delete করে। তবে saved dataset item-এর সঙ্গে সংশ্লিষ্ট media এর বাইরে থাকে। Self-hosted instance-এ এটি একটি Enterprise Edition feature এবং এর জন্য license key প্রয়োজন। License key না থাকলে data অনির্দিষ্টকাল সংরক্ষিত থাকে। Deletion পূর্বাবস্থায় ফেরানো যায় না। তাই ওই window-এর বাইরের history প্রয়োজন হলে আগে blob storage export configure করুন।

আমাকে কি Postgres এবং ClickHouse—দুটিরই backup নিতে হবে?

হ্যাঁ, কারণ দুটিতে ভিন্ন ধরনের data থাকে। Postgres-এ user, organisation, project এবং API key থাকে। ClickHouse-এ মূল trace data থাকে। শুধু Postgres restore করলে এমন একটি instance পাওয়া যাবে যাতে login করা যায়, কিন্তু কোনো data থাকবে না। MinIO bucket-এরও backup নিন। এতে Langfuse arrival-এর সময় সংরক্ষণ করা raw event থাকে, যা এই stack-এ source of truth-এর সবচেয়ে কাছাকাছি।

আমি কি বিদ্যমান OpenTelemetry setup-কে self-hosted Langfuse-এ নির্দেশ করতে পারি?

হ্যাঁ। Langfuse v4 এবং এর বর্তমান SDK OpenTelemetry-এর ওপর তৈরি। Anthropic ও OpenAI OTel instrumentation সরাসরি এতে export করে। Python-এ pip install langfuse opentelemetry-instrumentation-anthropic চালান, startup-এর সময় একবার AnthropicInstrumentor().instrument() call করুন এবং LANGFUSE_PUBLIC_KEY, LANGFUSE_SECRET_KEY ও LANGFUSE_BASE_URL-এ নিজের host নির্ধারণ করুন। Missing dashboard খোঁজার আগে langfuse.auth_check() দিয়ে নিশ্চিত করুন।

এই লেখাটি কি কাজে লেগেছে?