Odoo Performance Tuning: PostgreSQL and Server Optimization

Expert guide to Odoo 19 performance tuning. Covers PostgreSQL configuration, indexing, query optimization, Nginx caching, and server sizing for enterprise deployments.

E
ECOSIRE Research and Development Team
|2026年3月19日7 分で読める1.4k 語数|

Performance & Scalabilityシリーズの一部

完全ガイドを読む

Odoo パフォーマンス チューニング: PostgreSQL とサーバーの最適化

Odoo インスタンスが遅いと、生産性が失われ、ユーザーがイライラするため、コストがかかります。良いニュース: Odoo のパフォーマンスの問題のほとんどは、ハードウェアをアップグレードしなくても解決できます。悪いニュース: 根本原因を診断するには、Python、PostgreSQL、Nginx、Redis、ネットワーク層といったスタック全体を理解する必要があります。

このガイドでは、ボトルネックの特定、PostgreSQL のチューニング、Odoo サーバー設定の最適化、Nginx キャッシュの構成、ユーザー数とトランザクション量に合わせたインフラストラクチャの適切なサイジングなど、Odoo 19 Enterprise のパフォーマンス最適化ライフサイクル全体について説明します。

重要なポイント

  • PostgreSQL のチューニングにより、最大のパフォーマンス向上が実現します (一般的なインストールでは 50 ~ 300%)。
  • 共有バッファは開始点として利用可能な RAM の 25% に設定する必要があります
  • Odoo の ORM は、pg_stat_statements でキャッチできる N+1 クエリを生成します
  • 頻繁にフィルターされるフィールド (company_id、state、date) のインデックスは必須です
  • Nginx プロキシ キャッシュは、Odoo サーバーにアクセスせずに静的アセットを提供します
  • ワーカー構成は同時ユーザー容量に直接影響します
  • Redis セッションのキャッシュにより、認証のためのデータベースの負荷が軽減されます
  • バキュームと分析のスケジュールは、書き込み量の多い Odoo ワークロードに合わせて調整する必要があります

パフォーマンスのボトルネックを診断する

何かを調整する前に、実際に時間が費やされている部分を特定します。やみくもな最適化は労力を無駄にします。

Odoo クエリのログを有効にする:

# odoo.conf
[options]
log_level = info
logfile = /var/log/odoo/odoo.log

# For SQL query logging (development/staging only):
log_handler = odoo.sql_db:DEBUG

PostgreSQL の低速クエリのログを有効にする:

# /etc/postgresql/15/main/postgresql.conf
log_min_duration_statement = 1000  # Log queries taking > 1 second
log_line_prefix = '%t [%p]: [%l-1] user=%u,db=%d,app=%a,client=%h '
log_checkpoints = on
log_connections = on
log_lock_waits = on

pg_stat_statements (最も価値のある PostgreSQL 拡張機能) をインストールします:

-- Enable the extension
CREATE EXTENSION IF NOT EXISTS pg_stat_statements;

-- Find the top 20 slowest queries
SELECT
    round(total_exec_time::numeric, 2) AS total_ms,
    calls,
    round(mean_exec_time::numeric, 2) AS mean_ms,
    round((100 * total_exec_time / sum(total_exec_time) OVER ())::numeric, 2) AS pct,
    left(query, 100) AS query
FROM pg_stat_statements
ORDER BY total_exec_time DESC
LIMIT 20;

-- Reset statistics after tuning to measure improvement
SELECT pg_stat_statements_reset();

Odoo で N+1 クエリを特定する:

Odoo がレコードのリストをロードし、レコードごとに 1 つのクエリを作成して関連データをフェッチすると、N+1 クエリが発生します。 pg_stat_statements で次のようなパターンを探します。

-- This query appearing 500 times in a single page load = N+1 problem
SELECT * FROM res_partner WHERE id = $1

修正するには、Odoo の prefetch_ids メカニズムを使用するか、ORM クエリに select を追加します。

# Bad: triggers N+1 for partner on each order
for order in orders:
    print(order.partner_id.name)  # One query per order

# Good: prefetch partner data in one query
orders = self.env['sale.order'].search([...])
orders.mapped('partner_id')  # Forces prefetch
for order in orders:
    print(order.partner_id.name)  # No additional queries

PostgreSQL 構成のチューニング

PostgreSQL は、あらゆるハードウェアで実行できるように設計された保守的なデフォルトで出荷されます。 Odoo 運用サーバーの場合、これらのデフォルトを調整する必要があります。

メモリ設定 (32 GB RAM サーバーの場合):

# /etc/postgresql/15/main/postgresql.conf

# Shared buffers: 25% of RAM
shared_buffers = 8GB

# Work memory: per-operation memory for sorts/joins
# Start conservative, increase if you see disk sorts
work_mem = 64MB

# Maintenance work memory: for VACUUM, CREATE INDEX
maintenance_work_mem = 2GB

# Effective cache size: tells planner how much OS cache is available
# Set to 75% of total RAM
effective_cache_size = 24GB

# WAL settings for better write performance
wal_buffers = 256MB
checkpoint_completion_target = 0.9
checkpoint_timeout = 15min
max_wal_size = 4GB
min_wal_size = 1GB

接続設定:

# Maximum connections (Odoo workers × 2 + headroom)
max_connections = 200

# For connection pooling with PgBouncer
# If using PgBouncer, reduce to 50-100

クエリ プランナーの設定:

# Enable parallel query execution
max_parallel_workers_per_gather = 4
max_parallel_workers = 8
max_worker_processes = 16

# SSD storage: random_page_cost should equal seq_page_cost
random_page_cost = 1.1  # Default is 4.0 (for spinning disk)
seq_page_cost = 1.0

# Increase statistics target for better query plans on Odoo's large tables
default_statistics_target = 200

書き込み量の多いワークロード向けの自動バキューム チューニング:

Odoo のインベントリ、アカウンティング、およびメッセージング モジュールは、大量の INSERT/UPDATE トラフィックを生成します。デフォルトの自動バキューム設定では不十分です:

autovacuum = on
autovacuum_max_workers = 5
autovacuum_naptime = 30s
autovacuum_vacuum_threshold = 50
autovacuum_analyze_threshold = 50
autovacuum_vacuum_scale_factor = 0.01   # Vacuum when 1% of rows are dead
autovacuum_analyze_scale_factor = 0.005  # Analyze when 0.5% of rows change
autovacuum_vacuum_cost_delay = 2ms       # Reduce I/O throttling

重要なデータベースのインデックス

インデックスの欠落は、不適切な構成に次いで Odoo のパフォーマンスの問題の 2 番目に多い原因です。 Odoo は主キーと一部の外部キーのインデックスを作成しますが、一般的にフィルターされるフィールドの多くにはインデックスがありません。

pg_missing_fk_indexes ビューを使用して、欠落しているインデックスを確認します。

-- Find foreign keys without indexes
SELECT
    tc.table_name,
    kcu.column_name,
    ccu.table_name AS foreign_table_name,
    pg_relation_size(tc.table_name::regclass) AS table_size
FROM information_schema.table_constraints tc
JOIN information_schema.key_column_usage kcu
    ON tc.constraint_name = kcu.constraint_name
JOIN information_schema.constraint_column_usage ccu
    ON ccu.constraint_name = tc.constraint_name
WHERE tc.constraint_type = 'FOREIGN KEY'
AND NOT EXISTS (
    SELECT 1 FROM pg_indexes pi
    WHERE pi.tablename = tc.table_name
    AND pi.indexdef LIKE '%' || kcu.column_name || '%'
)
ORDER BY table_size DESC;

Odoo 19 の必須インデックス:

-- Sale orders (most queried table)
CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_sale_order_state
    ON sale_order(state);
CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_sale_order_company_date
    ON sale_order(company_id, date_order DESC);
CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_sale_order_partner
    ON sale_order(partner_id) WHERE state != 'cancel';

-- Account moves (invoicing)
CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_account_move_state_type
    ON account_move(state, move_type);
CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_account_move_company_date
    ON account_move(company_id, invoice_date DESC);
CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_account_move_partner
    ON account_move(partner_id) WHERE state = 'posted';

-- Account move lines (most queried for reconciliation)
CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_account_move_line_account_reconcile
    ON account_move_line(account_id, reconciled, date);
CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_account_move_line_move_date
    ON account_move_line(move_id, date);

-- Stock moves (inventory)
CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_stock_move_state_product
    ON stock_move(state, product_id);
CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_stock_quant_product_location
    ON stock_quant(product_id, location_id);

-- Mail messages (can grow very large)
CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_mail_message_res_model_id
    ON mail_message(res_model, res_id);
CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_mail_message_date
    ON mail_message(date DESC);

-- IR rule performance (access control)
CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_ir_rule_model_groups
    ON ir_rule(model_id);

Odoo ワーカーの構成

Odoo ワーカーの数によって、サーバーが処理できる同時リクエストの数が決まります。

ワーカー数の計算式:

Workers = (CPU_cores × 2) + 1
Memory per worker: 256MB - 512MB depending on workload

Example for 8 CPU cores, 32GB RAM:
Workers = (8 × 2) + 1 = 17
Memory check: 17 × 512MB = 8.5GB (well within 32GB)

odoo.conf ワーカー設定:

[options]
# Worker processes
workers = 17

# Limits to prevent runaway workers
limit_memory_hard = 2684354560  # 2.5GB hard limit (kills worker)
limit_memory_soft = 2147483648  # 2GB soft limit (triggers gc)
limit_time_cpu = 600            # CPU seconds per request
limit_time_real = 1200          # Wall clock seconds per request
limit_request = 8192            # Requests before worker restart

# Long polling (for live notifications)
longpolling_port = 8072

ワーカーのタイプを理解する:

Odoo は 2 種類のワーカーを使用します。

  1. HTTP ワーカー (workers 構成): すべての Web リクエストを処理します
  2. Cron ワーカー (1 予約): スケジュールされたアクションをバックグラウンドで実行します

cron ワーカーは常に実行されていますが、HTTP 容量にはカウントされません。ピーク負荷時でも少なくとも 1 つの cron ワーカーが利用可能であることを確認してください。


パフォーマンスのための Nginx 構成

Nginx は Odoo の前に位置し、TLS 終端、静的ファイルの提供、およびオプションでキャッシュを処理します。

高性能 Nginx 構成:

upstream odoo {
    server 127.0.0.1:8069 weight=1 fail_timeout=0;
}

upstream odoochat {
    server 127.0.0.1:8072 weight=1 fail_timeout=0;
}

# Cache zone for static assets
proxy_cache_path /var/cache/nginx/odoo
    levels=1:2
    keys_zone=odoo_cache:100m
    max_size=1g
    inactive=60m
    use_temp_path=off;

server {
    listen 443 ssl http2;
    server_name your-odoo.com;

    # Gzip compression
    gzip on;
    gzip_types text/plain text/css application/json application/javascript
               text/xml application/xml application/xml+rss text/javascript;
    gzip_min_length 1000;
    gzip_comp_level 6;

    # Static file caching
    location /web/static/ {
        proxy_cache odoo_cache;
        proxy_cache_valid 200 7d;
        proxy_cache_use_stale error timeout updating
            http_500 http_502 http_503 http_504;
        add_header X-Cache-Status $upstream_cache_status;
        expires 7d;
        proxy_pass http://odoo;
    }

    # Long polling for live chat/notifications
    location /web/longpolling {
        proxy_pass http://odoochat;
        proxy_read_timeout 3600s;
        proxy_connect_timeout 300s;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";
    }

    # Main application
    location / {
        proxy_pass http://odoo;
        proxy_set_header X-Forwarded-Host $host;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_read_timeout 720s;
        proxy_connect_timeout 300s;
        proxy_buffering on;
        proxy_buffer_size 128k;
        proxy_buffers 4 256k;
        proxy_busy_buffers_size 256k;

        # Security headers
        add_header X-Frame-Options DENY;
        add_header X-Content-Type-Options nosniff;
        add_header Referrer-Policy strict-origin-when-cross-origin;
    }
}

セッションとキャッシュ用の Redis

Redis は、セッション管理と Odoo の ORM キャッシュのデータベース負荷を大幅に軽減します。

Redis をインストールして構成します:

# Install Redis
sudo apt install redis-server

# Configure Redis for Odoo (max 4GB memory, LRU eviction)
sudo nano /etc/redis/redis.conf
# redis.conf
maxmemory 4gb
maxmemory-policy allkeys-lru
save ""  # Disable persistence for pure cache
tcp-keepalive 300

Redis を使用するように Odoo を構成します:

# odoo.conf
[options]
# Redis for session storage
session_redis_host = 127.0.0.1
session_redis_port = 6379
session_redis_prefix = odoo_session_

# Redis for IR rules and ORM cache
cache_redis_host = 127.0.0.1
cache_redis_port = 6379

モニタリングと継続的なパフォーマンス管理

PostgreSQL ログ分析用に pgBadger をセットアップします:

# Install pgBadger
sudo apt install pgbadger

# Generate report from PostgreSQL logs
pgbadger /var/log/postgresql/postgresql-15-main.log \
    -o /var/www/html/pgbadger/index.html \
    --format html \
    --top 20

監視すべき主要な指標:

メトリック警告しきい値クリティカルしきい値
ページの読み込み時間> 2 秒> 5 秒
DB クエリ時間> 100ms 平均平均 500 ミリ秒以上
労働者の記憶> 制限の 80%> 制限の 95%
PostgreSQL 接続> 最大値の 70%> 最大値の 90%
ディスク IOPS> プロビジョニング済みの 80%> プロビジョニング済みの 95%
キャッシュ ヒット率< 95%< 90%

よくある質問

50 人の同時 Odoo ユーザーに対する最小サーバー仕様はどれくらいですか?

中程度のトランザクション量を持つ 50 人の同時ユーザーの場合: 8 vCPU、32 GB RAM、500 GB SSD (NVMe を推奨)。 Odoo の PostgreSQL データベースは主な I/O ボトルネックであるため、CPU 速度よりも高速ストレージが重要です。 13 ワーカー (ヘッドルーム用に 8×2 ~ 3)、8 GB のshared_buffers を構成し、データベースが SSD ボリューム上にあることを確認します。

Odoo の遅さが Python によるものか PostgreSQL によるものかを診断するにはどうすればよいですか?

Odoo の組み込みプロファイラー (開発者モードでは [設定] → [テクニカル] → [パフォーマンス] → [プロファイラー]) を使用して、遅い操作を記録します。フレームグラフには、時間が Python コードに費やされているか、SQL 結果の待機に費やされているかが表示されます。 SQL クエリが大半を占める場合は、PostgreSQL のチューニングとインデックスに焦点を当てます。 Python が優勢な場合は、欠落している @api.depends キャッシュやカスタム コードの非効率性を探します。

接続プーリングには PgBouncer を使用する必要がありますか?

はい、30 を超える Odoo ワーカーまたは大量の API トラフィックを含むデプロイメントの場合。トランザクション モード プーリングの PgBouncer により、多くの Odoo ワーカーが実際の PostgreSQL 接続のより小さなプールを共有できるようになり、接続ごとのオーバーヘッドが削減されます。 PgBouncer を使用する場合は、PostgreSQL の max_connections を 50 ~ 100 に設定し、PgBouncer の pool_size を Odoo ワーカー数と一致するように設定します。

Odoo データベースで VACUUM ANALYZE を実行する頻度はどれくらいですか?

Autovacuum が正しく設定されていれば、これを自動的に処理します。上記のチューニング (積極的なスケール係数、より多くのワーカー) の後、自動バキュームはアクティブなテーブルで継続的に実行されるはずです。 SELECT schemaname, tablename, last_vacuum, last_autovacuum, last_analyze, last_autoanalyze FROM pg_stat_user_tables ORDER BY n_dead_tup DESC LIMIT 20; を実行して、テーブルが頻繁にバキューム処理されていることを確認します。

Odoo ワーカーが多すぎるとどのような影響がありますか?

各 Odoo ワーカーは最低 256 ~ 512 MB の RAM を消費します。ワーカーが多すぎるとメモリが枯渇し、ワーカーがクラッシュし (limit_memory_hard)、ユーザーに HTTP 500 エラーが発生します。さらに、PostgreSQL 接続 (ワーカー × max_db_connections) が多すぎると、データベースが過負荷になる可能性があります。式 (CPU×2+1) から始めて、負荷がかかっているメモリを監視し、必要に応じて下方調整します。


次のステップ

Odoo のパフォーマンス チューニングは反復的なプロセスです。 1 回のチューニング セッションで大幅な向上が得られますが、パフォーマンスを維持するには、継続的なモニタリング、定期的なインデックス分析、データ量の増加に伴う構成の調整が必要です。

ECOSIRE は、エンタープライズ展開向けの Odoo パフォーマンス監査を提供し、特定のワークロード、トランザクション パターン、およびインフラストラクチャに最も大きな影響を与える最適化を特定します。当社のエンジニアは、10 ユーザーの中小企業から 500 ユーザーの企業展開まで Odoo インストールを調整しました。

ECOSIRE に Odoo パフォーマンス監査をリクエスト →

現在のサーバー仕様、ユーザー数、発生している症状を共有していただければ、当社のチームが根本原因を特定し、優先順位の高い最適化計画を提供します。

E

執筆者

ECOSIRE Research and Development Team

ECOSIREでエンタープライズグレードのデジタル製品を開発。Odoo統合、eコマース自動化、AI搭載ビジネスソリューションに関するインサイトを共有しています。

WhatsAppでチャット