நீங்கள் அஜர்பைஜானில் ஒரு கார் புரோக்கரேஜ் நடத்தி, அமெரிக்காவிலிருந்து சேதமடைந்த வாகனங்களை (salvage vehicles) இறக்குமதி செய்யும் போது, உங்கள் மென்பொருள் சிக்கல்கள் சிலிக்கான் வேலி ஸ்டார்ட்அப்களின் சிக்கல்களைப் போல இருக்காது. நீங்கள் ஒரே நேரத்தில் மில்லியன் கணக்கான பயனர்களைக் கையாளுவதற்காக (optimizing) உழைக்கவில்லை. நீங்கள் தெளிவு, இயங்கும் நேரம் (uptime) மற்றும் நள்ளிரவில் பன்னிரண்டு மணி நேரத் தொலைவில் உள்ள ஒரு ஏல நிறுவனத்துடன் ஒருங்கிணைந்து செயல்படும்போது, சிக்கல்களை நீங்களே சரிசெய்யும் திறனுக்காக உழைக்கிறீர்கள். நான் AutoMakler-ஐ உருவாக்கும்போது இருந்த நிலை இதுதான். இந்தத் தளம் நேரடி ஏலத் தரவுகளைச் சேகரித்தல் (live auction scraping) மற்றும் Carfax தேடல்கள் முதல் விநியோக மதிப்பீடுகள் மற்றும் பணப் பரிவர்த்தனை செயலாக்கம் வரை அனைத்தையும் கையாள்கிறது. இது உண்மையான வாடிக்கையாளர்களுக்குச் சேவை செய்யும் ஒரு உண்மையான உற்பத்தி அமைப்பு (production system), மேலும் இது பெரும்பாலான டெவலப்பர்கள் "மிகவும் எளிமையான மற்றும் சலிப்பூட்டும் தொழில்நுட்பத் தொகுப்பு" (aggressively boring stack) என்று அழைப்பவற்றின் அடிப்படையில் இயங்குகிறது.
யாரும் முன்வந்து சொல்ல விரும்பாத தொழில்நுட்பத் தொகுப்பு (The Stack That Nobody Wants to Pitch)
இதில் React இல்லை. Vue இல்லை. Redis, Celery அல்லது WebSocket server இல்லை. இதன் backend, plain Python உடன் கூடிய FastAPI ஆகும். தரவுத்தளம் (database) PostgreSQL. Frontend என்பது Jinja2 templates, Bootstrap மற்றும் சிறிதளவு vanilla JavaScript ஆகியவற்றைப் பயன்படுத்தி server-rendered HTML மூலம் உருவாக்கப்பட்டது. Scraping செய்ய, நான் Playwright பயன்படுத்துகிறேன். அனைத்தும் நேரடியாக HTML-ஐ வழங்கும் ஒரு ஒற்றை Python process ஆக இயங்குகிறது.
இதில் build step இல்லை. ஆய்வு செய்ய node_modules கோப்புறைகள் இல்லை, configure செய்ய transpilers இல்லை, மற்றும் பின்தொடர வேண்டிய frontend framework மாற்றங்கள் இல்லை. நான் deploy செய்யும்போது, Python கோப்புகளையும் templates-களையும் நகர்த்துகிறேன், bundlers-களின் சிக்கலான வரிசையை (pipeline) ஒருங்கிணைக்கவில்லை. அந்த எளிமை ஒரு சமரசமல்ல. அதுவே இதன் முக்கிய நோக்கம்.
ஒரு Message Broker இல்லாமல் வேலைகளை (Jobs) எவ்வாறு வரிசைப்படுத்துவது?
நேரடி கார் ஏலத்தை (live car auction) scraping செய்வதை ஒரே நேரத்தில் (synchronously) செய்ய முடியாது. Playwright பக்கத்தை ஏற்றவும், JavaScript-ஐ இயக்கவும், தரவை எடுக்கவும் ஒரு single scrape பல வினாடிகள் எடுக்கலாம். இது நடக்கும்போது பயனரைத் தடுத்து நிறுத்துவது (blocking) ஒரு விருப்பமல்ல. வழக்கமான முறை Redis-ஐ நிறுவி, Celery-ஐ configure செய்து, ஒரு worker pool-ஐ உருவாக்குவதாகும். நான் இவை அனைத்தையும் தவிர்த்தேன்.
அதற்குப் பதிலாக, AutoMakler தனது சொந்த job queue ஆக Postgres-ஐப் பயன்படுத்துகிறது. ஒரு பயனர் scrape செய்யத் தூண்டும்போது, பயன்பாடு (application) 'pending' என்ற நிலையில் ஒரு புதிய வரிசையை (row) tasks table-இல் எழுதுகிறது. ஒரு asyncio background task அந்த வரிசையை எடுத்துக்கொண்டு browser scrape-ஐத் தொடங்குகிறது. இதற்கிடையில், browser அதன் நிலையைச் சரிபார்க்க ஒவ்வொரு மூன்று வினாடிகளுக்கும் ஒரு lightweight endpoint-ஐச் சரிபார்க்கிறது (polls). அந்த வரிசை 'completed' என மாறும்போது, பக்கம் புதுப்பிக்கப்பட்டு முடிவுகள் காட்டப்படுகின்றன.
இந்த முறை வேலை செய்கிறது, ஏனெனில் polling இடைவெளி துரிதமாகத் தெரிவதற்குப் போதுமான அளவு குறுகியதாகவும், அதே சமயம் சர்வரைப் பாதிக்காதவாறு போதுமான அளவு நீளமாகவும் உள்ளது. ஒரு கணினிக்கு மூன்று வினாடிகள் என்பது ஒரு யுகம், ஆனால் ஒரு வெளி ஏலத் தளத்திற்காகக் காத்திருக்கும் மனிதனுக்கு அது பெரிய விஷயமாகத் தெரியாது. தரவுத்தளம் இயல்பாகவே concurrency-யைக் கையாள்கிறது, மேலும் வேலைகள் Postgres-இல் வெறும் வரிசைகளாக (rows) இருப்பதால், Celery logs அல்லது Redis keys-களைத் தேடுவதற்குப் பதிலாக, ஒரு எளிய SQL query மூலம் நான் queue-வை ஆய்வு செய்யலாம்.
ஒரு Worker Pool இல்லாமல் சர்வரை எவ்வாறு இயங்க வைப்பது?
Browser automation அதிக நினைவகத்தை (memory) எடுத்துக்கொள்ளும். ஒரே நேரத்தில் பல Playwright instances-களைத் தொடங்கினால் உங்கள் சர்வர் முடங்கிவிடும். வழக்கமான தீர்வு, பெரும்பாலும் Redis மற்றும் Celery ஆகியவற்றைப் பயன்படுத்தி, concurrency வரம்புகளுடன் கூடிய ஒரு managed worker pool-ஐ உருவாக்குவதாகும். நான் ஒரு வரியில் Python-ஐப் பயன்படுத்துகிறேன்: asyncio.Semaphore.
இந்த semaphore ஒரே நேரத்தில் எத்தனை browser instances இயங்க முடியும் என்பதைக் கட்டுப்படுத்துகிறது. ஒரு புதிய scrape கோரிக்கை வரும்போது, அது உடனடியாக ஒரு இடத்தைப் பிடிக்கும் அல்லது ஒரு இடம் காலியாகும் வரை காத்திருக்கும். இவை அனைத்தும் ஒரே process-க்குள் நடப்பவை. தோல்வியடைய வெளிப்புற orchestrator எதுவும் இல்லை, அமைதியாக இறந்துபோகும் worker process எதுவும் இல்லை, மற்றும் கண்காணிக்க கூடுதல் infrastructure எதுவும் இல்லை. எனது நினைவகப் பயன்பாடு (memory) கணிக்கக்கூடியதாக இருக்கும், மேலும் சர்வரைப் பாதுகாக்கும் குறியீடு (code), அதைப் பயன்படுத்தும் குறியீட்டிற்கு அருகிலேயே இருக்கும், deployment manifest-இல் மறைந்து இருக்காது.
ஒரே Callback URL மூலம் பணத்தை வழிநடத்துதல் (Routing Money)
பணப் பரிவர்த்தனை (Payment processing) நான் மாற்ற முடியாத ஒரு கட்டுப்பாட்டை ஏற்படுத்தியது. எனது payment gateway ஒரு merchant account-க்கு சரியாக ஒரு callback URL மட்டுமே அனுமதிக்கிறது, ஆனால் எனக்கு அந்த ஒரே கணக்கின் மூலம் இரண்டு தனித்தனித் திட்டங்களுக்கான பரிவர்த்தனைகளைச் செய்ய வேண்டியிருந்தது. இரண்டாவது merchant profile-ஐ உருவாக்குவது என்பது கூடுதல் கட்டணங்கள், கூடுதல் இணக்கப்பாடுகள் (compliance) மற்றும் ஒரு சிறிய புரோக்கரேஜிற்குத் தேவையில்லாத கூடுதல் ஆவணப் பணிகளைக் குறிக்கும்.
இதற்கான தீர்வு, வாடிக்கையாளரை gateway-க்கு அனுப்புவதற்கு முன், order ID string-இல் திட்டத்தின் பெயரை நேரடியாக encode செய்வதாகும். callback எனது சர்வருக்கு வரும்போது, AutoMakler அந்த ID-யை decode செய்து, அந்தப் பணம் எந்தத் திட்டத்திற்குச் சொந்தமானது என்பதைக் கண்டறிந்து, சரியான உள் கையாளிக에게 (internal handler) அறிவிப்பை அனுப்புகிறது. ஏற்கனவே இருந்த தர்க்கம் (logic) மாற்றப்படாமல் இருந்தது. இது ஒரு 'additive design': நான் payment flow-வை மீண்டும் எழுதவில்லை, அடையாளங்காட்டியுடன் (identifier) கூடுதல் தகவல்களைச் சேர்த்தேன். இது பின்னோக்கிப் பார்க்கும்போது எளிதாகத் தோன்றும், ஆனால் பல மணிநேரக் கட்டமைப்புச் சிக்கல்களைத் (architectural gymnastics) தவிர்க்க உதவும் ஒரு நுட்பமாகும்.
WebSockets இல்லாமல் இயங்கும் Chat
வாடிக்கையாளர் ஆதரவு அரட்டை (Customer support chat) என்பது பொதுவாக பொறியாளர்கள் விட்டுக்கொடுத்து WebSockets-ஐச் சேர்க்கும் இடமாகும். எனக்கு உள்ளமைக்கப்பட்ட செய்தியிடல் (in-app messaging) தேவைப்பட்டது, ஆனால் அதே நேரத்தில் உள்கட்டமைப்புத் தேவையை (infrastructure footprint) மிகக் குறைவாக வைத்திருக்கவும் விரும்பினேன். எனவே, ஏலத் தரவு சேகரிப்பை (auction scrapes) இயக்கும் அதே polling முறையையே மீண்டும் பயன்படுத்தினேன்.
செய்திகள் Postgres-இல் சேமிக்கப்படுகின்றன. ஒரு பயனர் செய்தியை அனுப்பும்போது, அது அட்டவணையில் (table) எழுதப்படுகிறது. கிளையண்ட் (client) புதுப்பிப்புகளுக்காகத் தொடர்ந்து சரிபார்க்கிறது (polls), மேலும் UI புதிய செய்திகளையும் அவை வாசிக்கப்பட்டதற்கானத் தகவல்களையும் (read receipts) கிட்டத்தட்ட நிகழ்நேரத்தில் (near real-time) காட்டுகிறது. உரையாடல் அட்டவணை வளரும்போதும் இது வேகமாக இருக்க, தற்போது நடைபெற்று வரும் உரையாடல்களுக்கான வாசிக்கப்படாத செய்திகளை மட்டும் உள்ளடக்கிய ஒரு Postgres partial index-ஐச் சேர்த்தேன். தரவுத்தளம் பழைய வரலாற்றைத் தேடுவதில் சுழற்சிகளை (cycles) வீணாக்குவதில்லை, மேலும் query planner ஒரு குறுகிய index range scan மூலம் பெரும்பாலான அரட்டைத் தேடல்களை எளிதாகச் செய்துவிடுகிறது.
சில வினாடிகள் தாமதம் (latency) ஏற்றுக்கொள்ளக்கூடிய ஒரு ஆதரவு அரட்டைக்கு, இது முற்றிலும் போதுமானது. பயனர்களுக்குத் தேவையானத் தகவல் கிடைக்கிறது, மேலும் எனக்கு ஒரு காலாவதியான (stale) WebSocket இணைப்பைச் சரிசெய்யவோ (debug) அல்லது தனித்த ஒரு socket server-ஐ நிர்வகிக்கவோ வேண்டிய அவசியம் ஏற்படவில்லை.
உண்மையான குறைபாடுகள்
இந்த கட்டமைப்பு சில உண்மையான சமரசங்களைக் (trade-offs) கோருகிறது, அதைத் தவிர்த்துப் பேசுவது நேர்மையற்றதாக இருக்கும். Polling என்பது அதிகத் தொடர்புகளை (chatty) உருவாக்கும் முறை. ஒவ்வொரு மூன்று வினாடிகளுக்கும், ஒவ்வொரு செயல்பாட்டில் உள்ள கிளையண்ட்டும் சர்வருக்குத் தகவல் கோருகிறது. ஒரு நிலையான (persistent) socket இணைப்பிற்குத் தேவைப்படுவதை விட, இதில் அலைக்கற்றை (bandwidth) மற்றும் வினவல் சுமை (query load) அதிகமாக இருக்கும். ஒருவேளை Python செயல்முறை (process) மீண்டும் தொடங்கப்பட்டால், தற்போது நடந்து கொண்டிருக்கும் எந்தவொரு பின்னணிப் பணியும் (background task) உடனடியாக நின்றுவிடும், ஏனெனில் அதை மீண்டும் தொடங்குவதற்கு வெளிப்புறப் பணியாளர் (external worker) ஏதுமில்லை. இந்தப் பணிகள் சிறியவை மற்றும் மீண்டும் முயற்சிப்பதற்கான செலவு குறைவு என்பதால் நான் இதை ஏற்றுக்கொள்கிறேன். தோல்வியடைந்த ஒரு browser scrape-ஐ பயனர் எளிதாக மீண்டும் தொடங்க முடியும்.
இந்த அணுகுமுறைக்கும் ஒரு எல்லை உண்டு. AutoMakler ஆயிரக்கணக்கான ஒரே நேரத்தில் நடக்கும் scrapes-களைக் கையாள வேண்டியிருந்தால், polling முறையிலான இந்த ஒற்றை-செயல்முறை மாதிரி (single-process model) சிரமப்படும். ஆனால் நான் செய்யும் தொழில் அதுவல்ல. எனக்கு டஜன் கணக்கான ஒரே நேரத்தில் பயன்படுத்தும் பயனர்களுக்கு நம்பகத்தன்மை தேவைப்படுகிறது, தவிர...
