You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

基于Broker API的订单状态获取策略优化咨询:每秒轮询可行性与重试机制缺陷解决方案

Great question—let's tackle your two core issues first, then walk through a refined approach that addresses your concerns better than the options you've considered so far.

Core Issue 1: Is Polling Every Second Feasible?

Short answer: It's feasible in a limited window, but not as a long-term strategy. Polling every second non-stop will indeed clutter your logs, increase the risk of hitting API rate limits (you already handle this error case), and waste unnecessary network/CPU resources. However, for the first few seconds after placing an order, short-interval polling makes sense because many orders confirm quickly, and users expect timely feedback. The key is to limit this aggressive polling to a narrow initial window, then switch to a more efficient strategy for slower orders.

Core Issue 2: Handling Long-Running Order Confirmations

Your current 3-retry limit is clearly too low for orders that take 15-20s+ to confirm. The problem with your proposed solutions is that they either cut off too early (fixed timeout) or introduce unnecessary delays (pure exponential backoff). The best approach here is a dual-stage polling system that combines immediate user feedback with persistent background processing.

Refined Solution: Dual-Stage Polling + Persistent Background Queue

Here's how to implement it:

Stage 1: Immediate, Short-Interval Polling (First 10-15 Seconds)

  • For the first 10-15 seconds after the order is placed, poll every 1-2 seconds. This ensures users get fast updates for orders that confirm quickly, without excessive log spam.
  • Set a time-based limit here (e.g., 15 seconds) instead of a retry count—this is more intuitive for handling variable confirmation times.

Stage 2: Background Queue with Gentle Exponential Backoff

  • If the order status is still unresolved after Stage 1, add it to a persistent background queue (use something like Redis Queue, Celery with async support, or your framework's built-in task queue).
  • Background workers will poll the order status using a gentle exponential backoff strategy: start with a 2-second interval, multiply by 1.5 each retry, cap the interval at 30 seconds, and set a total timeout of 5-10 minutes (adjust based on your broker's maximum expected confirmation time).
  • When the worker finally gets a Confirmed or Rejected status, trigger your strategy_status_signal to update the user, and persist the final status to your database for future reference.

Modified Code Example

Here's how to adjust your get_order function to implement Stage 1, plus a pseudo-code snippet for the background queue worker:

import asyncio
from datetime import datetime, timedelta

async def get_order( subs_details: SubscriptionDetails, strike_unique_id: str, order_id: str ) -> tuple[str, float, str]:
    URL = conn.BALAJI_ORDER_STATUS
    headers = {"Content-Type": "application/json", "Authorization": conn.BALAJI_API_TOKEN}
    order_price = 0.0
    order_status = 'NA'
    error = ''
    start_time = datetime.now()
    initial_poll_window = timedelta(seconds=15)  # First 15s: aggressive polling
    initial_poll_interval = 1  # Poll every 1s

    try:
        (broker, token, strategy_id, activation_id) = extract_get_order_details(subs_details)
        payload = {"strike_id": strike_unique_id, "brokerName": broker, "token_id": token, "order_id": order_id}
        logger.info(f"Get Order payload: {payload}")

        # Stage 1: Immediate polling window
        while datetime.now() - start_time < initial_poll_window:
            async with SessionManager.session.post(URL, json=payload, headers=headers) as r:
                status_code = r.status
                res = await r.json()
                logger.info(f"Get order for {order_id} with response: {res}")

                if status_code != 200:
                    if 'invalid order id' in res.get('message', '').lower():
                        order_status = "Invalid"
                        error = "Invalid order ID"
                        break
                    await asyncio.create_task(strategy_status_signal(strategy_id, activation_id, 'ERROR', 'Internal Error'))
                    error = "API request failed"
                    break

                order_log = res.get("order_log", {})
                error = order_log.get("error_reason") or error
                order_price = order_log.get('order_price') or order_price
                order_status = order_log.get("order_status") or order_status

                # Handle rate limit: extend interval temporarily
                if f"API rate limit reached {broker}" in error:
                    logger.info(f"Rate limit hit for {broker}, extending poll interval")
                    await asyncio.sleep(initial_poll_interval * 2)
                    continue

                # Check for final status
                if order_status == "Confirmed":
                    logger.info(f"ORDER COMPLETED for {strike_unique_id} broker:{broker} & order_id:{order_id}")
                    await asyncio.create_task(strategy_status_signal(strategy_id, activation_id, 'SUCCESS', 'Order confirmed'))
                    break
                elif order_status == "Rejected":
                    await asyncio.create_task(strategy_status_signal(strategy_id, activation_id, 'ERROR', error))
                    logger.info(f"ORDER REJECTED for {strike_unique_id} broker:{broker} & order_id:{order_id}")
                    break

            await asyncio.sleep(initial_poll_interval)

        # If no final status after Stage 1, send to background queue
        if order_status not in ["Confirmed", "Rejected", "Invalid"]:
            logger.info(f"Order {order_id} not resolved in initial window, adding to background queue")
            # Replace with your actual queue enqueue logic (async-compatible)
            await background_queue.enqueue(
                poll_order_in_background,
                subs_details,
                strike_unique_id,
                order_id,
                strategy_id,
                activation_id
            )

    except Exception as e:
        logger.error(f"Error while fetching order details: {e}")
        error = str(e)

    return order_status, order_price, error

# Background worker function (runs in separate task/process)
async def poll_order_in_background(subs_details, strike_unique_id, order_id, strategy_id, activation_id):
    URL = conn.BALAJI_ORDER_STATUS
    headers = {"Content-Type": "application/json", "Authorization": conn.BALAJI_API_TOKEN}
    attempt = 0
    max_attempts = 20  # ~5 minutes total (2s → 3s → 4.5s ... capped at 30s)
    base_interval = 2
    max_interval = 30

    (broker, token, _, _) = extract_get_order_details(subs_details)
    payload = {"strike_id": strike_unique_id, "brokerName": broker, "token_id": token, "order_id": order_id}

    while attempt < max_attempts:
        try:
            async with SessionManager.session.post(URL, json=payload, headers=headers) as r:
                res = await r.json()
                order_log = res.get("order_log", {})
                order_status = order_log.get("order_status") or 'NA'
                error = order_log.get("error_reason") or ''

                if order_status == "Confirmed":
                    logger.info(f"BACKGROUND: ORDER COMPLETED for {order_id}")
                    await asyncio.create_task(strategy_status_signal(strategy_id, activation_id, 'SUCCESS', 'Order confirmed'))
                    return
                elif order_status == "Rejected":
                    logger.info(f"BACKGROUND: ORDER REJECTED for {order_id}")
                    await asyncio.create_task(strategy_status_signal(strategy_id, activation_id, 'ERROR', error))
                    return
                elif 'invalid order id' in res.get('message', '').lower():
                    logger.info(f"BACKGROUND: Invalid order ID {order_id}")
                    await asyncio.create_task(strategy_status_signal(strategy_id, activation_id, 'ERROR', 'Invalid order ID'))
                    return

            # Calculate backoff interval
            interval = min(base_interval * (1.5 ** attempt), max_interval)
            await asyncio.sleep(interval)
            attempt += 1

        except Exception as e:
            logger.error(f"BACKGROUND error polling order {order_id}: {e}")
            await asyncio.sleep(base_interval * 2)
            attempt += 1

    # If max attempts reached
    logger.info(f"BACKGROUND: Max attempts exceeded for order {order_id}")
    await asyncio.create_task(strategy_status_signal(strategy_id, activation_id, 'ERROR', 'Order status timed out'))

Key Improvements

  1. Balanced User Feedback: Stage 1 ensures fast updates for quick orders, while Stage 2 handles slow ones without blocking the user flow.
  2. Rate Limit Resilience: Temporarily extends polling intervals when hitting rate limits, instead of just retrying.
  3. Persistent Processing: The background queue ensures orders aren't lost if your service restarts, and the gentle backoff avoids overwhelming the broker's API.
  4. Clear Termination Conditions: Stops polling immediately for invalid order IDs or final statuses, wasting no resources.

Future-Proofing

Once your broker supports WebSockets, you can replace the background queue with a WebSocket subscription for order IDs. This will eliminate polling entirely for those orders, making the system even more efficient.

内容的提问来源于stack exchange,提问作者Himanshu Sharma

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.04.27 09:27:37