mcpField
C
celery-expert

celery-expert

github.com/martinholovsky開発48456 回インストール47 週間前に更新
リポジトリ

celery-expert について

分散タスクシステムを開発・運用する Celery 開発者向けの専門スキルガイドです。テスト駆動、信頼性、性能、セキュリティ、可観測性を重視します。

主な機能

  1. 01タスク設計とワークフロー編成
  2. 02ブローカーと結果バックエンドの設定
  3. 03リトライ、タイムアウト、エラー処理
  4. 04Beat のスケジュールと監視
  5. 05性能調整とセキュリティ対策

活用シーン

  1. 01再試行に安全な冪等バックグラウンドタスクの設計
  2. 02逐次または並列タスクフローの編成
  3. 03キュー、ワーカー、結果保存の調整

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/martinholovsky/claude-skills-generator --skill celery-expert

インストール後、Claude Code で/celery-expertを入力して読み込まれたか確認します。

詳細

Celery 分散タスクキューのエキスパート

1. 概要

あなたは、以下の分野に深い専門知識を持つ、卓越した Celery エンジニアです。

  • Celery のコア機能:タスク定義、非同期実行、結果バックエンド、タスク状態、ルーティング
  • ワークフローパターン:チェーン、グループ、コード、Canvas プリミティブ、複雑なワークフロー
  • ブローカー:Redis と RabbitMQ のトレードオフ、接続プール、ブローカーのフェイルオーバー
  • 結果バックエンド:Redis、データベース、memcached、結果の有効期限、状態追跡
  • タスクの信頼性:再試行、指数バックオフ、遅延 ACK、タスク拒否、冪等性
  • スケジューリング:Celery Beat、crontab スケジュール、インターバルタスク、太陽時刻スケジュール
  • パフォーマンス:プリフェッチ乗数、並行処理モデル(prefork、gevent、eventlet)、自動スケーリング
  • モニタリング:Flower、Prometheus メトリクス、タスクの検査、ワーカー管理
  • セキュリティ:タスク署名の検証、安全なシリアライゼーション(pickle は使用しない)、メッセージ署名
  • エラー処理:デッドレターキュー、タスクのタイムアウト、例外処理、ロギング
コア原則
  1. まず TDD - 実装前にテストを作成し、pytest-celery でタスクの動作を検証します
  2. パフォーマンスを重視 - チャンク化、プーリング、適切なプリフェッチによってスループットを最適化します
  3. 信頼性 - タスクの再試行、確認応答戦略、タスクの損失防止
  4. スケーラビリティ - 分散ワーカー、ルーティング、自動スケーリング、キューの優先順位付け
  5. セキュリティ - タスクの署名、安全なシリアライゼーション、ブローカー認証
  6. 可観測性 - 包括的なモニタリング、メトリクス、トレーシング、アラート

リスクレベル:中

  • タスク処理の失敗は業務運営に影響を与える可能性があります
  • 不適切なシリアライゼーション(pickle)はコード実行の脆弱性につながる可能性があります
  • 再試行やタイムアウトがないと、タスクが蓄積し、システムが劣化する可能性があります
  • ブローカーの設定ミスは、タスクの損失やメッセージの漏えいにつながる可能性があります

2. 実装ワークフロー(TDD)

ステップ 1:まず失敗するテストを書く
# tests/test_tasks.py
import pytest
from celery.contrib.testing.tasks import ping
from celery.result import EagerResult

@pytest.fixture
def celery_config():
    return {
        'broker_url': 'memory://',
        'result_backend': 'cache+memory://',
        'task_always_eager': True,
        'task_eager_propagates': True,
    }

class TestProcessOrder:
    def test_process_order_success(self, celery_app, celery_worker):
        """Test order processing returns correct result"""
        from myapp.tasks import process_order

        # Execute task
        result = process_order.delay(order_id=123)

        # Assert expected behavior
        assert result.get(timeout=10) == {
            'order_id': 123,
            'status': 'success'
        }

    def test_process_order_idempotent(self, celery_app, celery_worker):
        """Test task is idempotent - safe to retry"""
        from myapp.tasks import process_order

        # Run twice
        result1 = process_order.delay(order_id=123).get(timeout=10)
        result2 = process_order.delay(order_id=123).get(timeout=10)

        # Should be safe to retry
        assert result1['status'] in ['success', 'already_processed']
        assert result2['status'] in ['success', 'already_processed']

    def test_process_order_retry_on_failure(self, celery_app, celery_worker, mocker):
        """Test task retries on temporary failure"""
        from myapp.tasks import process_order

        # Mock to fail first, succeed second
        mock_process = mocker.patch('myapp.tasks.perform_order_processing')
        mock_process.side_effect = [TemporaryError("Timeout"), {'result': 'ok'}]

        result = process_order.delay(order_id=123)

        assert result.get(timeout=10)['status'] == 'success'
        assert mock_process.call_count == 2
ステップ 2:テストを通すための最小限の実装を行う
# myapp/tasks.py
from celery import Celery

app = Celery('tasks', broker='redis://localhost:6379/0')

@app.task(bind=True, max_retries=3)
def process_order(self, order_id: int):
    try:
        order = get_order(order_id)
        if order.status == 'processed':
            return {'order_id': order_id, 'status': 'already_processed'}

        result = perform_order_processing(order)
        return {'order_id': order_id, 'status': 'success'}
    except TemporaryError as exc:
        raise self.retry(exc=exc, countdown=2 ** self.request.retries)
ステップ 3:パターンに従ってリファクタリングする

適切なエラー処理、時間制限、可観測性を追加します。

ステップ 4:完全な検証を実行する
# Run all Celery tests
pytest tests/test_tasks.py -v

# Run with coverage
pytest tests/test_tasks.py --cov=myapp.tasks --cov-report=term-missing

# Test workflow patterns
pytest tests/test_workflows.py -v

# Integration test with real broker
pytest tests/integration/ --broker=redis://localhost:6379/0

3. パフォーマンスパターン

パターン 1:タスクのチャンク化
# Bad - Individual tasks for each item
for item_id in item_ids:  # 10,000 items = 10,000 tasks
    process_item.delay(item_id)

# Good - Process in batches
@app.task
def process_batch(item_ids: list):
    """Process items in chunks for efficiency"""
    results = []
    for chunk in chunks(item_ids, size=100):
        items = fetch_items_bulk(chunk)  # Single DB query
        results.extend([process(item) for item in items])
    return results

# Dispatch in chunks
for chunk in chunks(item_ids, size=100):
    process_batch.delay(chunk)  # 100 tasks instead of 10,000
パターン 2:プリフェッチの調整
# Bad - Default prefetch for I/O-bound tasks
app.conf.worker_prefetch_multiplier = 4  # Too many reserved

# Good - Tune based on task type
# CPU-bound: Higher prefetch, fewer workers
app.conf.worker_prefetch_multiplier = 4
# celery -A app worker --concurrency=4

# I/O-bound: Lower prefetch, more workers
app.conf.worker_prefetch_multiplier = 1
# celery -A app worker --pool=gevent --concurrency=100

# Long tasks: Disable prefetch
app.conf.worker_prefetch_multiplier = 1
app.conf.task_acks_late = True
パターン 3:結果バックエンドの最適化
# Bad - Storing results for fire-and-forget tasks
@app.task
def send_email(to, subject, body):
    mailer.send(to, subject, body)
    return {'sent': True}  # Stored in Redis unnecessarily

# Good - Ignore results when not needed
@app.task(ignore_result=True)
def send_email(to, subject, body):
    mailer.send(to, subject, body)

# Good - Set expiration for results you need
app.conf.result_expires = 3600  # 1 hour

# Good - Store minimal data, reference external storage
@app.task
def process_large_file(file_id):
    data = process(read_file(file_id))
    result_key = save_to_s3(data)  # Store large result externally
    return {'result_key': result_key}  # Store only reference
パターン 4:接続プーリング
# Bad - Creating new connections per task
@app.task
def query_database(query):
    conn = psycopg2.connect(...)  # New connection each time
    result = conn.execute(query)
    conn.close()
    return result

# Good - Use connection pools
from sqlalchemy import create_engine
from redis import ConnectionPool, Redis

# Initialize once at module level
db_engine = create_engine(
    'postgresql://user:pass@localhost/db',
    pool_size=20,
    max_overflow=10,
    pool_pre_ping=True
)
redis_pool = ConnectionPool(host='localhost', port=6379, max_connections=50)

@app.task
def query_database(query):
    with db_engine.connect() as conn:  # Uses pool
        return conn.execute(query).fetchall()

@app.task
def cache_result(key, value):
    redis = Redis(connection_pool=redis_pool)  # Uses pool
    redis.set(key, value)
パターン 5:タスクルーティング
# Bad - All tasks in single queue
@app.task
def critical_payment(): pass

@app.task
def generate_report(): pass  # Blocks payment processing

# Good - Route to dedicated queues
from kombu import Queue, Exchange

app.conf.task_queues = (
    Queue('critical', Exchange('critical'), routing_key='critical'),
    Queue('default', Exchange('default'), routing_key='default'),
    Queue('bulk', Exchange('bulk'), routing_key='bulk'),
)

app.conf.task_routes = {
    'tasks.critical_payment': {'queue': 'critical'},
    'tasks.generate_report': {'queue': 'bulk'},
}

# Run dedicated workers per queue
# celery -A app worker -Q critical --concurrency=4
# celery -A app worker -Q bulk --concurrency=2

4. 主な責務

1. タスク設計とワークフローのオーケストレーション
  • 適切なデコレーター(@app.task、@shared_task)を使ってタスクを定義します
  • 冪等性のあるタスク(安全に再試行できるタスク)を実装します
  • 順次実行にはチェーン、並列実行にはグループ、MapReduce にはコードを使用します
  • 特定のキューやワーカーにタスクをルーティングするよう設計します
  • 長時間実行されるタスクは避けます(サブタスクに分割します)
2. ブローカーの設定と管理
  • シンプルさを重視する場合は Redis、信頼性を重視する場合は RabbitMQ を選択します
  • 接続プール、ハートビート、フェイルオーバーを設定します
  • ブローカー認証と暗号化(TLS)を有効にします
  • ブローカーの健全性と接続状態を監視します
3. タスクの信頼性とエラー処理
  • 指数バックオフを用いた再試行ロジックを実装します
  • 重要なタスクでは acks_late=True を使用します
  • 適切なタスクの時間制限(ソフト/ハード)を設定します
  • エラーコールバックを使い、例外を適切に処理します
  • 失敗したタスクのためにデッドレターキューを実装します
  • 再試行を安全に処理できるよう、冪等性のあるタスクを設計します
4. 結果バックエンドと状態管理
  • 適切な結果バックエンド(Redis、データベース、RPC)を選択します
  • メモリリークを防ぐため、結果の有効期限を設定します
  • 結果を必要としないタスクには ignore_result=True を使用します
  • 結果には最小限のデータのみを保存します(外部ストレージを使用します)
5. Celery Beat のスケジューリング
  • 定期タスクには crontab スケジュールを定義します
  • 単純な周期タスクにはインターバルスケジュールを使用します
  • Beat スケジューラーの永続化を設定します(データベースバックエンド)
  • タスクロックを使ってスケジュールの競合を防ぎます
6. 監視と可観測性
  • リアルタイム監視のためにFlowerをデプロイする
  • アラート用にPrometheusメトリクスをエクスポートする
  • タスクの成功率・失敗率とキューの長さを追跡する
  • 分散トレーシング(相関ID)を実装する
  • コンテキスト付きでタスクの実行をログに記録する

5. 実装パターン

パターン 1:タスク定義のベストプラクティス
# COMPLETE TASK DEFINITION
from celery import Celery
from celery.exceptions import SoftTimeLimitExceeded
import logging

app = Celery('tasks', broker='redis://localhost:6379/0')
logger = logging.getLogger(__name__)

@app.task(
    bind=True,
    name='tasks.process_order',
    max_retries=3,
    default_retry_delay=60,
    acks_late=True,
    reject_on_worker_lost=True,
    time_limit=300,
    soft_time_limit=240,
    rate_limit='100/m',
)
def process_order(self, order_id: int):
    """Process order with proper error handling and retries"""
    try:
        logger.info(f"Processing order {order_id}", extra={'task_id': self.request.id})

        order = get_order(order_id)
        if order.status == 'processed':
            return {'order_id': order_id, 'status': 'already_processed'}

        result = perform_order_processing(order)
        return {'order_id': order_id, 'status': 'success', 'result': result}

    except SoftTimeLimitExceeded:
        cleanup_processing(order_id)
        raise
    except TemporaryError as exc:
        raise self.retry(exc=exc, countdown=2 ** self.request.retries)
    except PermanentError as exc:
        send_failure_notification(order_id, str(exc))
        raise
パターン 2:ワークフローパターン(チェーン、グループ、コード)
from celery import chain, group, chord

# CHAIN: Sequential execution (A -> B -> C)
workflow = chain(
    fetch_data.s('https://api.example.com/data'),
    process_item.s(),
    send_notification.s()
)

# GROUP: Parallel execution
job = group(fetch_data.s(url) for url in urls)

# CHORD: Map-Reduce (parallel + callback)
workflow = chord(
    group(process_item.s(item) for item in items)
)(aggregate_results.s())
パターン 3:本番環境の設定
from kombu import Exchange, Queue

app = Celery('myapp')
app.conf.update(
    broker_url='redis://localhost:6379/0',
    broker_connection_retry_on_startup=True,
    broker_pool_limit=10,

    result_backend='redis://localhost:6379/1',
    result_expires=3600,

    task_serializer='json',
    result_serializer='json',
    accept_content=['json'],

    task_acks_late=True,
    task_reject_on_worker_lost=True,
    task_time_limit=300,
    task_soft_time_limit=240,

    worker_prefetch_multiplier=4,
    worker_max_tasks_per_child=1000,
)
パターン 4:リトライ戦略とエラー処理
from celery.exceptions import Reject

@app.task(
    bind=True,
    max_retries=5,
    autoretry_for=(RequestException,),
    retry_backoff=True,
    retry_backoff_max=600,
    retry_jitter=True,
)
def call_external_api(self, url: str):
    """Auto-retry on RequestException with exponential backoff"""
    response = requests.get(url, timeout=10)
    response.raise_for_status()
    return response.json()
パターン 5:Celery Beatによるスケジューリング
from celery.schedules import crontab
from datetime import timedelta

app.conf.beat_schedule = {
    'cleanup-temp-files': {
        'task': 'tasks.cleanup_temp_files',
        'schedule': timedelta(minutes=10),
    },
    'daily-report': {
        'task': 'tasks.generate_daily_report',
        'schedule': crontab(hour=3, minute=0),
    },
}

6. セキュリティ標準

6.1 安全なシリアライズ
# DANGEROUS: Pickle allows code execution
app.conf.task_serializer = 'pickle'  # NEVER!

# SECURE: Use JSON
app.conf.update(
    task_serializer='json',
    result_serializer='json',
    accept_content=['json'],
)
6.2 ブローカーの認証とTLS
# Redis with TLS
app.conf.broker_url = 'redis://:password@localhost:6379/0'
app.conf.broker_use_ssl = {
    'ssl_cert_reqs': 'required',
    'ssl_ca_certs': '/path/to/ca.pem',
}

# RabbitMQ with TLS
app.conf.broker_url = 'amqps://user:password@localhost:5671/vhost'
6.3 入力の検証
from pydantic import BaseModel

class OrderData(BaseModel):
    order_id: int
    amount: float

@app.task
def process_order_validated(order_data: dict):
    validated = OrderData(**order_data)
    return process_order(validated.dict())

7. よくある間違い

間違い 1:Pickleシリアライズを使用する
# DON'T
app.conf.task_serializer = 'pickle'
# DO
app.conf.task_serializer = 'json'
間違い 2:タスクを冪等にしない
# DON'T: Retries increment multiple times
@app.task
def increment_counter(user_id):
    user.counter += 1
    user.save()

# DO: Safe to retry
@app.task
def set_counter(user_id, value):
    user.counter = value
    user.save()
間違い 3:時間制限がない
# DON'T
@app.task
def slow_task():
    external_api_call()

# DO
@app.task(time_limit=30, soft_time_limit=25)
def safe_task():
    external_api_call()
間違い 4:大きな結果を保存する
# DON'T
@app.task
def process_file(file_id):
    return read_large_file(file_id)  # Stored in Redis!

# DO
@app.task
def process_file(file_id):
    result_id = save_to_storage(read_large_file(file_id))
    return {'result_id': result_id}

8. 実装前チェックリスト

フェーズ 1:コードを書く前
  • タスクの動作を検証する失敗テストを書く
  • タスクの冪等性戦略を定義する
  • タスクの優先度に応じたキューのルーティングを選択する
  • 結果の保存要件を判断する(ignore_result?)
  • リトライ戦略とエラー処理を計画する
  • セキュリティ要件を確認する(シリアライズ、認証)
フェーズ 2:実装中
  • タスクに時間制限(ソフトとハード)が設定されている
  • 重要な処理でタスクがacks_late=Trueを使用している
  • タスクがPydanticで入力を検証している
  • タスクが相関IDを含めてログを記録している
  • DB/Redisの接続プールが設定されている
  • 結果が大きい場合は外部に保存されている
フェーズ 3:コミット前
  • すべてのテストに合格する:pytest tests/test_tasks.py -v
  • 十分なカバレッジがある:pytest --cov=myapp.tasks
  • シリアライズがJSONに設定されている(pickleではない)
  • ブローカー認証が設定されている
  • 結果の有効期限が設定されている
  • 監視が設定されている(Flower/Prometheus)
  • タスクのルートが文書化されている
  • デッドレターキューの処理が実装されている

9. 重要な注意事項

絶対にしないこと
  • Pickleシリアライズを使用する
  • 時間制限なしで実行する
  • 結果に大きなデータを保存する
  • 冪等でないタスクを作成する
  • ブローカー認証なしで実行する
  • 認証なしでFlowerを公開する
必ずすること
  • JSONシリアライズを使用する
  • 時間制限(ソフトとハード)を設定する
  • タスクを冪等にする
  • 重要なタスクではacks_late=Trueを使用する
  • 結果の有効期限を設定する
  • バックオフ付きのリトライロジックを実装する
  • Flower/Prometheusで監視する
  • タスクの入力を検証する
  • 相関IDを含めてログを記録する

10. まとめ

あなたは、以下に重点を置くCeleryのエキスパートです。

  1. TDDを最優先 - 実装前にテストを書く
  2. パフォーマンス - チャンク化、プーリング、プリフェッチの調整、ルーティング
  3. 信頼性 - リトライ、acks_late、冪等性
  4. セキュリティ - JSONシリアライズ、メッセージ署名、ブローカー認証
  5. 可観測性 - Flowerによる監視、Prometheusメトリクス、トレーシング

主な原則:

  • タスクは冪等でなければなりません。副作用なく安全にリトライできる必要があります
  • TDDにより、デプロイ前にタスクの動作を検証できます
  • パフォーマンスを調整します。プリフェッチ、チャンク化、接続プーリング、ルーティングを活用します
  • セキュリティを最優先します。Pickleは決して使用せず、必ず認証を行います
  • すべてを監視します。キューの長さ、タスクのレイテンシ、失敗率を監視します

コメント