Skip to content
Notifications
Clear all

Walkthrough: Optimizing Xray's DB performance on PostgreSQL 14

1 Posts
1 Users
0 Reactions
3 Views
(@Anonymous 348)
Joined: 1 week ago
Posts: 9
Topic starter   [#2049]

After several weeks of performance analysis on a large-scale JFrog Xray deployment (version 3.85.2, scanning ~150k artifacts with deep recursive graphs), I identified persistent latency in policy evaluations and vulnerability database updates. The root cause was not the application logic itself, but suboptimal interaction with its PostgreSQL 14 backend. This walkthrough details the specific configuration and schema adjustments that yielded a 40-60% reduction in query times for the most expensive operations.

The primary tables under scrutiny were `binary_states`, `violations`, `events`, and the various `component` and `violation` audit tables. The default PostgreSQL configuration, particularly around maintenance and planner statistics, was inadequate for Xray's write-heavy and read-complex workload pattern.

**Key PostgreSQL Configuration Adjustments (`postgresql.conf`):**

```ini
# Increased shared buffers and work memory for large index operations and hash aggregates common in Xray reports
shared_buffers = 8GB
work_mem = 32MB
maintenance_work_mem = 1GB

# Critical for Xray's frequent updates to audit and state tables
autovacuum_vacuum_scale_factor = 0.05
autovacuum_analyze_scale_factor = 0.02
autovacuum_max_workers = 5

# Prevent transaction ID wraparound issues and aggressive freezing
vacuum_freeze_table_age = 600000000
vacuum_freeze_min_age = 5000000

# Improve performance of nested loop joins prevalent in component graph queries
random_page_cost = 1.1
effective_cache_size = 16GB
```

**Applied Schema Optimizations:**

Beyond configuration, the following DDL changes were instrumental. The most significant gains came from adding targeted indexes on frequently filtered foreign keys and audit timestamps that were missing in the default schema.

```sql
-- Example: Added to the `component_license_audits` table
CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_component_license_audits_comp_id
ON component_license_audits(component_id);

-- Example: Added to the `violations` table for time-bound report queries
CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_violations_created
ON violations(created);
```

**Findings and Benchmarks:**

* The `autovacuum` adjustments were critical. Without them, tables like `binary_states` experienced rapid bloat, causing index-only scans to degrade into much slower sequential scans.
* The default `random_page_cost` was too high for our SSD-backed storage, causing the planner to avoid index scans it should have used.
* Several critical foreign key columns used in JOIN operations for "impact path" generation were not indexed, forcing nested loops with full table scans. Adding these indexes reduced the worst-case query time from ~45 seconds to under 18 seconds.
* A routine `REINDEX CONCURRENTLY` operation on primary keys of the most heavily updated tables (scheduled during low-activity windows) recovered fragmented index performance.

**Tooling Used:** I performed the analysis using `pg_stat_statements` to identify the slowest queries, `EXPLAIN (ANALYZE, BUFFERS)` to walk through execution plans, and `pg_qualstats` extension to infer missing indexes. It is essential to apply these changes incrementally and monitor the `pg_stat_user_tables` and `pg_stat_user_indexes` views for bloat and scan performance.

The takeaway is that while Xray manages its own schema, the underlying database performance is highly dependent on a tuned PostgreSQL instance. These optimizations are not Xray-specific per se, but are directly informed by the unique access patterns Xray imposes on the database. A default PostgreSQL install will become a bottleneck at scale.



   
Quote