API Engineering

एंटी-एब्यूज ह्यूरिस्टिक्स को ट्रिगर किए बिना API दर सीमाओं को कम करना

4 min read Published Updated 808 words

अधिकांश API दर सीमा कार्यान्वयन को भोले प्रॉक्सी रोटेशन द्वारा आसानी से बायपास किया जा सकता है — लेकिन वही रोटेशन मिनटों के भीतर दुरुपयोग-विरोधी ह्यूरिस्टिक्स को ट्रिगर कर देता है। असली चुनौती केवल सीमा के अंदर रहना नहीं है, बल्कि बॉट जैसा दिखे बिना ऐसा करना है। इसके लिए दो अलग-अलग दर सीमा स्कोप (प्रति-IP और प्रति-कुंजी) को समझना, जिटर के साथ बैकऑफ लागू करना, पूल रोटेशन को यादृच्छिक बनाना, और ऑर्गेनिक ट्रैफिक की नकल करने के लिए अनुरोध समय को आकार देना आवश्यक है। प्रॉक्सी पूल पर स्तरित टोकन बकेट पैटर्न एक मजबूत आधार प्रदान करता है।

Per-IP बनाम Per-Key: दर सीमा के दो अक्ष

दर सीमाएँ कम से कम दो स्वतंत्र आयामों पर काम करती हैं: स्रोत IP और API कुंजी (या टोकन)। एक एकल कुंजी प्रति घंटे 5,000 अनुरोधों की अनुमति दे सकती है (GitHub की प्रमाणित सीमा), लेकिन एक ही IP से वही कुंजी कम बर्स्ट सीलिंग पर थ्रॉटल की जा सकती है। अप्रमाणित अनुरोध और भी अधिक प्रतिबंधित होते हैं — आमतौर पर प्रति IP प्रति घंटे 60 अनुरोध। IP आयाम को अनदेखा करना 429 Too Many Requests (RFC 6585) प्रतिक्रिया को ट्रिगर करने का सबसे तेज़ तरीका है। एक प्रॉक्सी पूल को किसी एकल मूल को संतृप्त करने से बचने के लिए अनुरोधों को कई IP में वितरित करना चाहिए, लेकिन प्रत्येक IP अभी भी एक ही कुंजी साझा करता है। यदि कुंजी की वैश्विक सीमा 5,000/घंटा है और आपके पास 50 प्रॉक्सी हैं, तो प्रत्येक प्रॉक्सी कुंजी-स्तरीय काउंटर फायर होने से पहले प्रति घंटे केवल 100 अनुरोध कर सकता है। कोड की एक भी पंक्ति लिखने से पहले दोनों सीमाओं का मानचित्रण करें।

जिटर के साथ बैकऑफ: विनम्र और पूर्वानुमेय के बीच का अंतर

बिना जिटर के एक्सपोनेंशियल बैकऑफ एक फिंगरप्रिंट है। सर्वर अनुरोधों को पूरी तरह से दोगुने अंतराल पर आते देखते हैं और उन्हें स्वचालित के रूप में चिह्नित करते हैं। समाधान पूर्ण जिटर है: sleep(random.uniform(0, min(cap, base * 2 ** attempt)))। यह पुनः प्रयासों को समय विंडो में फैलाता है, जिससे पैटर्न वास्तविक उपयोगकर्ताओं के विस्फोट से अप्रभेद्य हो जाता है। AWS का अपना दस्तावेज़ीकरण एक्सपोनेंशियल बैकऑफ पर ठीक इसी कारण से जिटर की सिफारिश करता है। निम्नलिखित Python स्निपेट एक टोकन बकेट लागू करता है जो एक निश्चित दर पर रिफिल होता है, खाली होने पर जिटर के साथ सोता है, और प्रत्येक सफल अधिग्रहण पर प्रॉक्सी को घुमाता है:

import time
import random
from collections import deque

class TokenBucketProxyPool:
    def __init__(self, proxies, rate, burst):
        self.proxies = deque(proxies)
        self.rate = rate          # tokens per second
        self.burst = burst
        self.tokens = burst
        self.last_refill = time.monotonic()

    def refill(self):
        now = time.monotonic()
        elapsed = now - self.last_refill
        self.tokens = min(self.burst, self.tokens + elapsed * self.rate)
        self.last_refill = now

    def acquire(self):
        while True:
            self.refill()
            if self.tokens >= 1:
                self.tokens -= 1
                self.proxies.rotate(1)   # simple rotation, but see section below
                return self.proxies[0]
            else:
                sleep_time = (1 - self.tokens) / self.rate
                jitter = random.uniform(0, sleep_time * 0.5)
                time.sleep(sleep_time + jitter)

# Usage: pool = TokenBucketProxyPool(proxies, rate=2.0, burst=10)
#         proxy = pool.acquire()   # blocks until token available

प्रॉक्सी पूल रोटेशन: यादृच्छिक बनाएँ, राउंड-रॉबिन नहीं

प्रॉक्सी सूची के माध्यम से अनुक्रमिक राउंड-रॉबिन पता लगाने योग्य है। एक सर्वर जो 1.2.3.4, फिर 5.6.7.8, फिर 9.10.11.12 से लॉकस्टेप में अनुरोध देखता है, वह पैटर्न को IP में सहसंबंधित करेगा। इसके बजाय, प्रतिस्थापन के साथ यादृच्छिक रूप से प्रॉक्सी चुनें, और प्रति-प्रॉक्सी कूलडाउन जोड़ें। सार्वजनिक प्रॉक्सी निर्देशिकाएं 60–80% विफलता दर रिपोर्ट करती हैं — एक प्रॉक्सी जो 429 या कनेक्शन त्रुटि लौटाती है, उसे कम से कम 60 सेकंड के लिए डेड क्यू में डिमोट किया जाना चाहिए। एक भारित यादृच्छिक चयन लागू करें जहां हाल ही में सफल प्रॉक्सी चुने जाने की अधिक संभावना हो। ऊपर का टोकन बकेट स्पष्टता के लिए एक सरल rotate(1) का उपयोग करता है; उत्पादन में इसे random.choice() से बदलें और प्रति-प्रॉक्सी विफलता गणना ट्रैक करें।

अनुरोध शेपिंग: रेखा पार किए बिना माध्य की नकल करें

एक आदर्श टोकन बकेट और प्रॉक्सी पूल के साथ भी, प्रति सेकंड ठीक 2.0 की स्थिर अनुरोध दर अप्राकृतिक है। वास्तविक उपयोगकर्ता रुकते हैं, बैच करते हैं, और अनुरोधों के बीच अंतराल बदलते हैं। प्रत्येक acquire() कॉल से पहले एक छोटी यादृच्छिक देरी (जैसे, time.sleep(random.uniform(0.1, 0.5))) जोड़ें, और टोकन बकेट की दर को हर कुछ मिनटों में ±10% तक बदलें। हालांकि, ऐसी देरी न जोड़ें जो API की दस्तावेजित दर सीमा विंडो से अधिक हो — यह उद्देश्य को विफल करता है। लक्ष्य सीमा के भीतर रहना है जबकि वैध ग्राहकों के समूह के रूप में दिखना है। अत्यधिक शेपिंग (जैसे, मानव-जैसी टाइपिंग देरी डालना) व्यर्थ प्रयास है; API अनुरोध आवृत्ति की परवाह करता है, कीस्ट्रोक के बीच के समय की नहीं।

नैतिक शमन: जानें कि रेखा कहाँ है

यहां वर्णित हर तकनीक एक वैध अनुकूलन है — जब तक यह API की सेवा की शर्तों का उल्लंघन नहीं करती। अधिकांश ToS स्पष्ट रूप से “दर सीमाओं को दरकिनार करने” या “सेवा तक पहुंचने के लिए स्वचालित साधनों का उपयोग करने” पर प्रतिबंध लगाते हैं। ToS पढ़ना वैकल्पिक नहीं है। यदि API प्रति कुंजी प्रति मिनट 100 अनुरोधों की अनुमति देता है, तो 50 प्रॉक्सी का उपयोग करना जिनमें से प्रत्येक प्रति मिनट 2 अनुरोध करता है, अनुपालन है। 100 प्रॉक्सी का उपयोग करना जिनमें से प्रत्येक प्रति मिनट 100 अनुरोध करता है, अनुपालन नहीं है — यह दुरुपयोग है, चाहे आपका जिटर कितना भी चतुर क्यों न हो। अंतर इरादा और मात्रा है। प्रॉक्सी पूल पर स्तरित एक टोकन बकेट एक उपकरण है; वही उपकरण जो नैतिक रूप से सार्वजनिक निर्देशिका को स्क्रैप करता है, एक छोटे API पर DDoS हमला करने के लिए भी इस्तेमाल किया जा सकता है। अपने दर सीमा लक्ष्यों का दस्तावेजीकरण करें, 429s के लिए अपने लॉग का ऑडिट करें, और कभी भी दस्तावेजित प्रति-कुंजी सीलिंग से अधिक न हों। यदि आपको अधिक थ्रूपुट की आवश्यकता है, तो उच्च सीमा मांगें या समर्पित योजना के लिए भुगतान करें।