అజర్బైజాన్లో మీరు కార్ బ్రోకరేజ్ నడుపుతూ, యునైటెడ్ స్టేట్స్ నుండి సాల్వేజ్ (salvage) వాహనాలను దిగుమతి చేసుకుంటున్నప్పుడు, మీ సాఫ్ట్వేర్ సమస్యలు సిలికాన్ వ్యాలీ స్టార్టప్ల సమస్యల కంటే భిన్నంగా ఉంటాయి. మీరు మిలియన్ల కొద్దీ కన్కరెంట్ యూజర్ల కోసం ఆప్టిమైజ్ చేయడం లేదు. మీరు స్పష్టత (clarity), అప్టైమ్ (uptime), మరియు అర్ధరాత్రి సమయంలో పన్నెండు టైమ్ జోన్ల దూరంలో ఉన్న ఒక ఆక్షన్ హౌస్తో సమన్వయం చేసుకుంటూ, సమస్యలను మీరే స్వయంగా పరిష్కరించుకునే సామర్థ్యం కోసం ఆప్టిమైజ్ చేస్తున్నారు. నేను AutoMaklerని నిర్మించినప్పుడు నేను సరిగ్గా ఇటువంటి పరిస్థితిలోనే ఉన్నాను. ఈ ప్లాట్ఫారమ్ లైవ్ ఆక్షన్ స్క్రాపింగ్ (live auction scraping) మరియు Carfax లుకప్ల నుండి డెలివరీ అంచనాలు మరియు పేమెంట్ ప్రాసెసింగ్ వరకు అన్నింటినీ నిర్వహిస్తుంది. ఇది నిజమైన కస్టమర్లకు సేవలు అందించే ఒక రియల్ ప్రొడక్షన్ సిస్టమ్, మరియు ఇది చాలా మంది డెవలపర్లు "అగ్రెసివ్గా బోరింగ్ స్టాక్" (aggressively boring stack) అని పిలిచే సాంకేతికతపై నడుస్తుంది.
The Stack That Nobody Wants to Pitch
ఇక్కడ React లేదు. Vue లేదు. Redis లేదు, Celery లేదు, మరియు WebSocket సర్వర్ కూడా లేదు. బ్యాకెండ్ అనేది plain Python తో కూడిన FastAPI. డేటాబేస్ PostgreSQL. ఫ్రంటెండ్ అనేది Jinja2 templates, Bootstrap మరియు కొద్దిపాటి vanilla JavaScript ఉపయోగించి సర్వర్-రెండర్ చేయబడిన HTML. స్క్రాపింగ్ కోసం, నేను Playwrightని ఉపయోగిస్తాను. అంతా ఒకే ఒక Python ప్రాసెస్గా నడుస్తుంది, ఇది నేరుగా HTMLని అందిస్తుంది.
ఇక్కడ ఎలాంటి build step లేదు. ఆడిట్ చేయడానికి node_modules ఫోల్డర్లు లేవు, కాన్ఫిగర్ చేయడానికి transpilers లేవు, మరియు అనుసరించడానికి ఫ్రంటెండ్ ఫ్రేమ్వర్క్ మార్పులు (churn) లేవు. నేను డిప్లాయ్ చేసినప్పుడు, నేను పైథాన్ ఫైల్స్ మరియు టెంప్లేట్లను మాత్రమే తరలిస్తాను, బండలర్ల (bundlers) పైప్లైన్ను నిర్వహించను. ఆ సరళత అనేది రాజీ కాదు. అదే అసలు ఉద్దేశ్యం.
How to Queue Jobs Without a Message Broker
లైవ్ కార్ ఆక్షన్ను స్క్రాప్ చేయడం సింక్రోనస్గా (synchronously) జరగలేదు. Playwright పేజీని లోడ్ చేసి, JavaScriptని ఎగ్జిక్యూట్ చేసి, డేటాను సేకరించే క్రమంలో ఒకే ఒక స్క్రేప్ చేయడానికి కొన్ని సెకన్ల సమయం పట్టవచ్చు. ఇది జరుగుతున్నప్పుడు యూజర్ను బ్లాక్ చేయడం అనేది సాధ్యం కాదు. సాధారణ పద్ధతి ప్రకారం Redisని ఇన్స్టాల్ చేయాలి, Celeryని కాన్ఫిగర్ చేయాలి మరియు వర్కర్ పూల్ను (worker pool) సిద్ధం చేయాలి. నేను వాటన్నింటినీ వదిలేశాను.
దానికి బదులుగా, AutoMakler తన స్వంత జాబ్ క్యూ (job queue) కోసం Postgresని ఉపయోగిస్తుంది. ఒక యూజర్ స్క్రేప్ను ప్రారంభించినప్పుడు, అప్లికేషన్ 'pending' స్టేటస్తో tasks టేబుల్లో ఒక కొత్త రో (row)ను రాస్తుంది. ఒక asyncio బ్యాక్గ్రౌండ్ టాస్క్ ఆ రోను తీసుకుని బ్రౌజర్ స్క్రేప్ను ప్రారంభిస్తుంది. ఈలోగా, బ్రౌజర్ స్టేటస్ను తనిఖీ చేయడానికి ప్రతి మూడు సెకన్లకు ఒక లైట్వెయిట్ ఎండ్పాయింట్ను పోల్ (poll) చేస్తుంది. ఆ రో 'completed'గా అప్డేట్ అయినప్పుడు, పేజీ రిఫ్రెష్ అయ్యి ఫలితాలను చూపుతుంది.
ఈ పద్ధతి పనిచేయడానికి కారణం, పోలింగ్ ఇంటర్వల్ (polling interval) స్పందనను (responsive) అనుభూతి చెందేంత తక్కువగా ఉండటమే కాకుండా, సర్వర్పై భారం పడకుండా ఉండటానికి తగినంత సమయం ఉండటం. కంప్యూటర్కు మూడు సెకన్లు అనేది చాలా ఎక్కువ సమయం, కానీ బాహ్య ఆక్షన్ సైట్ కోసం వేచి ఉన్న మనిషికి అది పెద్దగా అనిపించదు. డేటాబేస్ కన్కరెన్సీని (concurrency) నేరుగా నిర్వహిస్తుంది, మరియు జాబ్స్ కేవలం Postgresలో రోస్ (rows) మాత్రమే కాబట్టి, నేను Celery లాగ్స్ లేదా Redis కీలను వెతకాల్సిన అవసరం లేకుండా, ఒక సాధారణ SQL క్వెరీతో క్యూను తనిఖీ చేయగలను.
Keeping the Server Alive Without a Worker Pool
బ్రౌజర్ ఆటోమేషన్ మెమరీని ఎక్కువగా వినియోగిస్తుంది. ఒకేసారి చాలా Playwright ఇన్స్టాన్స్లను ప్రారంభిస్తే మీ సర్వర్ కుప్పకూలిపోతుంది. దీనికి సాధారణ పరిష్కారం కన్కరెన్సీ పరిమితులతో కూడిన మేనేజ్డ్ వర్కర్ పూల్, ఇది తరచుగా అదే Redis మరియు Celery కాంబినేషన్తో పనిచేస్తుంది. నేను కేవలం ఒక లైన్ పైథాన్ ఉపయోగిస్తాను: asyncio.Semaphore.
సెమాఫోర్ (semaphore) ఎన్ని బ్రౌజర్ ఇన్స్టాన్స్లు ఒకేసారి నడవవచ్చో పరిమితిని విధిస్తుంది. కొత్త స్క్రేప్ రిక్వెస్ట్ వచ్చినప్పుడు, అది వెంటనే ఒక స్లాట్ను తీసుకుంటుంది లేదా ఒక స్లాట్ ఖాళీ అయ్యే వరకు వేచి ఉంటుంది. ఇదంతా ఒకే ప్రాసెస్లో జరుగుతుంది. విఫలం కావడానికి ఎటువంటి బాహ్య ఆర్కెస్ట్రేటర్ (orchestrator) లేదు, నిశ్శబ్దంగా ఆగిపోయే వర్కర్ ప్రాసెస్ లేదు, మరియు పర్యవేక్షించడానికి అదనపు ఇన్ఫ్రాస్ట్రక్చర్ లేదు. నా మెమరీ వినియోగం ఊహించదగినదిగా ఉంటుంది, మరియు సర్వర్ను రక్షించే కోడ్, దానిని ఉపయోగించే కోడ్కు సరిగ్గా పక్కనే ఉంటుంది, డిప్లాయ్మెంట్ మేనిఫెస్ట్లో దాగి ఉండదు.
Routing Money with One Callback URL
పేమెంట్ ప్రాసెసింగ్ నేను మార్చలేని ఒక పరిమితిని తీసుకువచ్చింది. నా పేమెంట్ గేట్వే ప్రతి మర్చంట్ అకౌంట్కు సరిగ్గా ఒకే ఒక callback URLని అనుమతిస్తుంది, కానీ నేను ఆ ఒక్క అకౌంట్ ద్వారా రెండు వేర్వేరు ప్రాజెక్ట్ల లావాదేవీలను ప్రాసెస్ చేయాల్సి ఉంది. రెండవ మర్చంట్ ప్రొఫైల్ను సృష్టించడం అంటే అదనపు ఫీజులు, అదనపు కంప్లయన్స్ మరియు అదనపు పేపర్వర్క్ అని అర్థం, దీనికి ఒక చిన్న బ్రోకరేజ్ వద్ద సమయం ఉండదు.
దీనికి పరిష్కారం ఏమిటంటే, కస్టమర్ను గేట్వేకి పంపే ముందు ఆర్డర్ ID స్ట్రింగ్లో ప్రాజెక్ట్ పేరును నేరుగా ఎన్కోడ్ చేయడం. కాల్బ్యాక్ నా సర్వర్కు వచ్చినప్పుడు, AutoMakler ఆ IDని డీకోడ్ చేస్తుంది, పేమెంట్ ఏ ప్రాజెక్ట్కు చెందుతుందో గుర్తిస్తుంది మరియు నోటిఫికేషన్ను సరైన ఇంటర్నల్ హ్యాండ్లర్కు పంపిస్తుంది. ఉన్న లాజిక్ను మార్చలేదు. ఇది అడిటివ్ డిజైన్ (additive design): నేను పేమెంట్ ఫ్లోను తిరిగి రాయలేదు, కేవలం ఐడెంటిఫైయర్కు మరికొంత కాంటెక్స్ట్ను జోడించాను. ఇది వెనక్కి తిరిగి చూస్తే చాలా స్పష్టంగా అనిపించే ఒక హ్యాక్, కానీ ఇది గంటల కొద్దీ ఆర్కిటెక్చరల్ కష్టాలను (architectural gymnastics) తగ్గిస్తుంది.
Chat That Works Without WebSockets
కస్టమర్ సపోర్ట్ చాట్ విషయంలో ఇంజనీర్లు సాధారణంగా లొంగిపోయి WebSockets ని జోడిస్తారు. నాకు ఇన్-యాప్ మెసేజింగ్ కావాలి, కానీ అదే సమయంలో ఇన్ఫ్రాస్ట్రక్చర్ ఫుట్ప్రింట్ను కూడా చాలా తక్కువగా ఉంచాలని ఉంది. అందుకే, ఆక్షన్ స్కేప్స్కు (auction scrapes) ఉపయోగించే అదే పోలింగ్ స్ట్రాటజీని నేను మళ్ళీ ఉపయోగించాను.
మెసేజ్లు Postgresలో నిల్వ చేయబడతాయి. ఒక యూజర్ మెసేజ్ పంపినప్పుడు, అది టేబుల్లో సేవ్ అవుతుంది. క్లయింట్ అప్డేట్ల కోసం పోలింగ్ చేస్తుంది, మరియు UI కొత్త మెసేజ్లను మరియు రీడ్ రిసీట్లను (read receipts) దాదాపు రియల్ టైమ్లో చూపిస్తుంది. సంభాషణ టేబుల్ పెరిగే కొద్దీ దీనిని వేగంగా ఉంచడానికి, నేను యాక్టివ్ సంభాషణలలోని చదవని (unread) మెసేజ్లను మాత్రమే కవర్ చేసేలా ఒక Postgres partial index ని జోడించాను. డేటాబేస్ పాత హిస్టరీని స్కాన్ చేయడంలో సమయాన్ని వృధా చేయదు, మరియు క్వెరీ ప్లానర్ (query planner) ఒక టైట్ ఇండెక్స్ రేంజ్ స్కాన్ ద్వారా చాలా చాట్ లుకప్లను సులభంగా పూర్తి చేయగలదు.
కొన్ని సెకన్ల లాటెన్సీ (latency) అంగీకరించదగిన సపోర్ట్ చాట్ కోసం, ఇది పూర్తిగా సరిపోతుంది. యూజర్లకు వారికి కావాల్సిన ఫీడ్బ్యాక్ అందుతుంది, మరియు నేను ఎప్పుడూ ఒక పాత (stale) WebSocket కనెక్షన్ను డీబగ్ చేయాల్సిన లేదా విడిగా ఒక సాకెట్ సర్వర్ను నిర్వహించాల్సిన అవసరం రాలేదు.
నిజమైన లోపాలు
ఈ ఆర్కిటెక్చర్లో వాస్తవమైన లాభనష్టాలు (trade-offs) ఉన్నాయి, అలా కాదని చెప్పడం నిజాయితీ లేని పని అవుతుంది. పోలింగ్ వల్ల నెట్వర్క్ రిక్వెస్ట్లు ఎక్కువగా ఉంటాయి. ప్రతి మూడు సెకన్లకు, ప్రతి యాక్టివ్ క్లయింట్ సర్వర్ను హిట్ చేస్తుంది. ఒక పర్సిస్టెంట్ సాకెట్ కనెక్షన్ కంటే బ్యాండ్విడ్త్ మరియు క్వెరీ లోడ్ ఎక్కువగా ఉంటాయి. ఒకవేళ Python ప్రాసెస్ రీస్టార్ట్ అయితే, ప్రస్తుతం నడుస్తున్న (in-flight) ఏదైనా బ్యాక్గ్రౌండ్ టాస్క్ వెంటనే ఆగిపోతుంది, ఎందుకంటే దానిని తిరిగి ప్రారంభించడానికి ఎటువంటి ఎక్స్టర్నల్ వర్కర్ ఉండదు. టాస్క్లు చిన్నవి మరియు మళ్ళీ ప్రయత్నించడం (retry) వల్ల అయ్యే ఖర్చు తక్కువ కాబట్టి నేను దీనిని అంగీకరిస్తున్నాను. విఫలమైన బ్రౌజర్ స్కేప్ను యూజర్ సులభంగా మళ్ళీ ప్రారంభించవచ్చు.
ఈ విధానానికి ఒక పరిమితి కూడా ఉంది. ఒకవేళ AutoMakler వేల సంఖ్యలో ఒకేసారి స్కేప్స్ను నిర్వహించాల్సి వస్తే, పోలింగ్తో కూడిన సింగిల్-ప్రాసెస్ మోడల్ ఒత్తిడికి గురవుతుంది. కానీ నేను చేస్తున్న వ్యాపారం అది కాదు. నాకు డజన్ల కొద్దీ కన్కరెంట్ యూజర్ల కోసం విశ్వసనీయత కావాలి, కానీ...
