SDK Performance Guide

December 17, 2025 · View on GitHub

Complete guide to OilPriceAPI Python SDK performance characteristics and optimization best practices.


Performance Baselines

Based on production measurements and integration tests.

Current Price Queries

OperationAverage TimeP95P99
prices.get() (single)150ms300ms500ms
prices.get_multiple() (3 commodities)200ms400ms600ms
prices.get_multiple() (10 commodities)250ms500ms800ms

Expected Performance:

  • Single price: <500ms
  • Multiple prices: <1s for up to 10 commodities

Historical Data Queries

Query TypeDate RangeRecordsAvg TimeMax TimeEndpoint Used
1 Day1 day~241-2s10s/past_day
1 Week7 days~7-85-10s30s/past_week
1 Month30 days~3015-25s60s/past_month
1 Year365 days~36560-85s120s/past_year

Expected Performance:

  • 1 week queries: <30s
  • 1 month queries: <60s
  • 1 year queries: <120s

v1.4.1 Bug: All queries used /past_year endpoint, causing 1-week queries to take 67s instead of <10s.

v1.4.2 Fix: Intelligent endpoint selection reduces query times by 7x for short ranges.

Pagination Performance

Per PageTotal RecordsAPI CallsAvg Time
1003654 calls75s
5003651 call70s
10003651 call68s

Recommendation: Use per_page=1000 for large datasets to minimize API calls.


Optimization Techniques

1. Use Appropriate Date Ranges

Bad (slow):

# Fetches 365 days when only need 7
client.historical.get(
    commodity="WTI_USD",
    start_date="2024-01-01",  # Way too far back
    end_date="2024-01-07"     # Only need 7 days
)
# Takes: ~70s (uses /past_year endpoint)

Good (fast):

# Fetches exactly what you need
from datetime import datetime, timedelta

end_date = datetime.now()
start_date = end_date - timedelta(days=7)

client.historical.get(
    commodity="WTI_USD",
    start_date=start_date.strftime("%Y-%m-%d"),
    end_date=end_date.strftime("%Y-%m-%d")
)
# Takes: ~8s (uses /past_week endpoint)

2. Batch Multiple Commodities

Bad (multiple API calls):

# Makes 3 separate API calls
wti = client.prices.get("WTI_USD")          # 150ms
brent = client.prices.get("BRENT_CRUDE_USD") # 150ms
natgas = client.prices.get("NATURAL_GAS_USD") # 150ms
# Total: ~450ms + network overhead

Good (single API call):

# Makes 1 API call
prices = client.prices.get_multiple([
    "WTI_USD",
    "BRENT_CRUDE_USD",
    "NATURAL_GAS_USD"
])
# Total: ~200ms

3. Increase Pagination Limit

Bad (many small requests):

# Default per_page=100 means 4 API calls for 365 records
history = client.historical.get(
    commodity="WTI_USD",
    start_date="2024-01-01",
    end_date="2024-12-31",
    per_page=100  # Too small
)
# Takes: ~75s (4 API calls)

Good (one large request):

# per_page=1000 means 1 API call for 365 records
history = client.historical.get(
    commodity="WTI_USD",
    start_date="2024-01-01",
    end_date="2024-12-31",
    per_page=1000  # Optimal
)
# Takes: ~68s (1 API call)

4. Use Async Client for Parallel Queries

Bad (sequential):

# Queries run one after another
client = OilPriceAPI()
wti_history = client.historical.get("WTI_USD", ...)    # 70s
brent_history = client.historical.get("BRENT_CRUDE_USD", ...)  # 70s
# Total: ~140s

Good (parallel):

import asyncio
from oilpriceapi import AsyncOilPriceAPI

async def get_all_history():
    async with AsyncOilPriceAPI() as client:
        wti_task = client.historical.get("WTI_USD", ...)
        brent_task = client.historical.get("BRENT_CRUDE_USD", ...)

        wti, brent = await asyncio.gather(wti_task, brent_task)
        return wti, brent

# Total: ~70s (parallel execution)

5. Reuse Client Instance

Bad (creates new client each time):

def get_price(commodity):
    client = OilPriceAPI()  # New connection each time
    price = client.prices.get(commodity)
    client.close()
    return price

# Each call has connection overhead
for commodity in commodities:
    price = get_price(commodity)  # Slow

Good (reuse connection):

def get_prices(commodities):
    with OilPriceAPI() as client:  # Single connection
        return [client.prices.get(c) for c in commodities]

prices = get_prices(commodities)  # Fast

6. Specify Timeout for Long Queries

Bad (may timeout):

# Uses default 30s timeout
# 1-year query takes 70s -> Timeout!
client.historical.get(
    commodity="WTI_USD",
    start_date="2024-01-01",
    end_date="2024-12-31"
)

Good (appropriate timeout):

# SDK v1.4.2+ automatically sets timeout based on date range
# 1-year query uses 120s timeout
client.historical.get(
    commodity="WTI_USD",
    start_date="2024-01-01",
    end_date="2024-12-31"
)

# Or manually specify for multi-year queries
client.historical.get(
    commodity="WTI_USD",
    start_date="2020-01-01",
    end_date="2024-12-31",
    timeout=180  # 3 minutes for 5 years
)

Performance Pitfalls

Pitfall 1: Polling for Latest Prices

Anti-Pattern:

import time

while True:
    price = client.prices.get("WTI_USD")
    print(f"WTI: ${price.value}")
    time.sleep(1)  # Poll every second

Problems:

  • Wastes API quota
  • Unnecessary load on API
  • Price only updates ~every 5 minutes

Solution:

# Poll at reasonable interval
import time

while True:
    price = client.prices.get("WTI_USD")
    print(f"WTI: ${price.value}")
    time.sleep(300)  # Poll every 5 minutes

Better Solution (for real-time):

# Use WebSocket for real-time updates (if available)
# Or increase polling interval to match update frequency

Pitfall 2: Fetching All Historical Data

Anti-Pattern:

# Fetches ALL data since beginning of time
history = client.historical.get(
    commodity="WTI_USD",
    start_date="1990-01-01",  # 35 years!
    end_date="2025-01-01"
)
# Takes: 5+ minutes, may timeout

Solution:

# Fetch data in chunks
from datetime import datetime, timedelta

def get_historical_range(client, commodity, years=1):
    """Get historical data in 1-year chunks."""
    all_data = []
    end_date = datetime.now()

    for year in range(years):
        start_date = end_date - timedelta(days=365)

        chunk = client.historical.get(
            commodity=commodity,
            start_date=start_date.strftime("%Y-%m-%d"),
            end_date=end_date.strftime("%Y-%m-%d")
        )

        all_data.extend(chunk.data)
        end_date = start_date

    return all_data

# Get 5 years in 5 chunks of 1 year each
data = get_historical_range(client, "WTI_USD", years=5)

Pitfall 3: Not Using Context Manager

Anti-Pattern:

client = OilPriceAPI()
price = client.prices.get("WTI_USD")
# Forget to close - connection leak

Solution:

# Auto-closes connection
with OilPriceAPI() as client:
    price = client.prices.get("WTI_USD")

Pitfall 4: Ignoring Retry Logic

Anti-Pattern:

# Disable retries for "performance"
client = OilPriceAPI(max_retries=0)

# One network blip = permanent failure
price = client.prices.get("WTI_USD")  # Fails on transient error

Solution:

# Use default retry logic (max_retries=3)
client = OilPriceAPI()  # Retries on 429, 500, 502, 503, 504

# Retries protect against transient failures
price = client.prices.get("WTI_USD")  # Resilient

Caching Strategies

Client-Side Caching

Basic In-Memory Cache:

from datetime import datetime, timedelta
from functools import lru_cache

@lru_cache(maxsize=100)
def get_cached_price(commodity, cache_key):
    """Cache prices for 5 minutes."""
    client = OilPriceAPI()
    return client.prices.get(commodity)

# Cache key changes every 5 minutes
def get_current_price(commodity):
    cache_key = int(datetime.now().timestamp() / 300)
    return get_cached_price(commodity, cache_key)

# First call: API request (150ms)
price1 = get_current_price("WTI_USD")

# Second call within 5 min: cached (<1ms)
price2 = get_current_price("WTI_USD")

Redis Cache (for multi-process):

import redis
import json
from datetime import timedelta

redis_client = redis.Redis(host='localhost', port=6379)

def get_cached_price(client, commodity):
    """Cache price in Redis for 5 minutes."""
    cache_key = f"oilprice:{commodity}"

    # Check cache
    cached = redis_client.get(cache_key)
    if cached:
        return json.loads(cached)

    # Fetch from API
    price = client.prices.get(commodity)

    # Cache for 5 minutes
    redis_client.setex(
        cache_key,
        timedelta(minutes=5),
        json.dumps(price.dict())
    )

    return price

When to Cache

Good candidates for caching:

  • Latest prices (updates every 5 minutes)
  • Historical data (never changes)
  • Commodity metadata
  • Static reference data

Don't cache:

  • Real-time price updates (if using WebSocket)
  • User-specific data
  • Data that changes frequently

Monitoring Performance

Track Response Times

import time
from oilpriceapi import OilPriceAPI

def timed_query(operation, *args, **kwargs):
    """Execute operation and measure time."""
    start = time.time()

    try:
        result = operation(*args, **kwargs)
        duration = time.time() - start
        print(f"✓ {operation.__name__}: {duration:.2f}s")
        return result

    except Exception as e:
        duration = time.time() - start
        print(f"✗ {operation.__name__}: {duration:.2f}s - {e}")
        raise

# Usage
client = OilPriceAPI()

price = timed_query(client.prices.get, "WTI_USD")
# Output: ✓ get: 0.15s

history = timed_query(
    client.historical.get,
    commodity="WTI_USD",
    start_date="2024-01-01",
    end_date="2024-12-31"
)
# Output: ✓ get: 72.30s

Set Performance Budgets

class PerformanceBudget:
    """Assert operations complete within budget."""

    BUDGETS = {
        "prices.get": 0.5,           # 500ms
        "prices.get_multiple": 1.0,  # 1 second
        "historical.1_week": 30,     # 30 seconds
        "historical.1_month": 60,    # 60 seconds
        "historical.1_year": 120,    # 120 seconds
    }

    @staticmethod
    def check(operation, duration):
        budget = PerformanceBudget.BUDGETS.get(operation)
        if budget and duration > budget:
            print(f"⚠️  PERFORMANCE: {operation} took {duration:.2f}s (budget: {budget}s)")
            return False
        return True

# Usage
start = time.time()
price = client.prices.get("WTI_USD")
duration = time.time() - start

PerformanceBudget.check("prices.get", duration)

Troubleshooting Slow Queries

Diagnostic Checklist

  1. Check SDK Version

    import oilpriceapi
    print(oilpriceapi.__version__)
    # Should be >= 1.4.2 for optimal performance
    
  2. Check Date Range

    # Are you fetching more data than needed?
    days = (end_date - start_date).days
    print(f"Fetching {days} days of data")
    
  3. Check Network Latency

    # Test API connectivity
    curl -w "%{time_total}\n" https://api.oilpriceapi.com/v1/health
    
  4. Check Pagination

    # Are you making too many small requests?
    print(f"Per page: {per_page}, Total pages: {total_records / per_page}")
    
  5. Enable Debug Logging

    import logging
    logging.basicConfig(level=logging.DEBUG)
    
    # Will show all HTTP requests and timing
    client = OilPriceAPI()
    

Common Issues & Fixes

SymptomLikely CauseSolution
Timeout on 1-year querySDK v1.4.1 bugUpgrade to v1.4.2+
Slow historical queriesUsing wrong endpointUpgrade to v1.4.2+
Many small requestsLow pagination limitIncrease per_page to 1000
Connection errorsNot reusing clientUse context manager
High memory usageLoading too much dataFetch in chunks

Performance Testing

Benchmark Script

"""
Benchmark SDK performance.

Usage: python benchmark.py
"""

import time
from datetime import datetime, timedelta
from oilpriceapi import OilPriceAPI

def benchmark():
    client = OilPriceAPI()

    tests = [
        ("Current price", lambda: client.prices.get("WTI_USD")),
        ("Multiple prices (3)", lambda: client.prices.get_multiple([
            "WTI_USD", "BRENT_CRUDE_USD", "NATURAL_GAS_USD"
        ])),
        ("1 week history", lambda: client.historical.get(
            "WTI_USD",
            (datetime.now() - timedelta(days=7)).strftime("%Y-%m-%d"),
            datetime.now().strftime("%Y-%m-%d")
        )),
        ("1 month history", lambda: client.historical.get(
            "WTI_USD",
            (datetime.now() - timedelta(days=30)).strftime("%Y-%m-%d"),
            datetime.now().strftime("%Y-%m-%d")
        )),
        ("1 year history", lambda: client.historical.get(
            "WTI_USD",
            "2024-01-01",
            "2024-12-31"
        )),
    ]

    print("="*60)
    print("OilPriceAPI SDK Performance Benchmark")
    print("="*60)
    print()

    for name, operation in tests:
        start = time.time()
        try:
            result = operation()
            duration = time.time() - start
            print(f"✓ {name:30s} {duration:8.2f}s")
        except Exception as e:
            duration = time.time() - start
            print(f"✗ {name:30s} {duration:8.2f}s - {e}")

    print()

if __name__ == "__main__":
    benchmark()

Summary

Quick Performance Checklist

  • Using SDK v1.4.2+ (has endpoint optimization)
  • Fetching minimal date range needed
  • Using per_page=1000 for large datasets
  • Batching multiple commodity queries
  • Reusing client instances
  • Using context managers
  • Caching where appropriate
  • Setting appropriate timeouts
  • Monitoring performance
  • Running performance tests

Expected Performance

  • Current prices: <500ms
  • Multiple prices (10): <1s
  • 1 week history: <30s
  • 1 month history: <60s
  • 1 year history: <120s

When to Optimize

Optimize when:

  • Queries consistently exceed budgets
  • User experience is affected
  • API quota is being wasted
  • Costs are higher than expected

Don't optimize prematurely - profile first!