💡وقتی چیزی خراب میشود ، حتی اگر از نظر فنی «مسئولش نباشی» وارد عمل شو.
👣 مالکیت (Ownership) به این معنا نیست که همهچیز را خودت انجام دهی؛
بلکه یعنی آنقدر اهمیت بدهی که مطمئن شوی هیچ مشکلی نادیده گرفته نمیشود.
👨💻 بعضی از بهترین مهندسانی که میشناسم هرگز نمیگویند:
❌ «این کار من نیست.»
✅ آنها میگویند: «بیا ببینیم چطور میتونیم حلش کنیم.»
🧭 این طرز فکر تو را به نیرویی برای تغییر مثبت تبدیل میکند.
مالکیت، پایه و اساس رهبری است.
و دوباره تأکید میکنم:
مالکیت به این معنا نیست که خودت همه چیز را تعمیر کنی.
بلکه یعنی دیگران را هم همراه کنی، به آنها قدرت بدهی،
و نشان دهی که آنها هم میتوانند بخشی از تغییر مثبت باشند. 💪
👣 مالکیت (Ownership) به این معنا نیست که همهچیز را خودت انجام دهی؛
بلکه یعنی آنقدر اهمیت بدهی که مطمئن شوی هیچ مشکلی نادیده گرفته نمیشود.
👨💻 بعضی از بهترین مهندسانی که میشناسم هرگز نمیگویند:
❌ «این کار من نیست.»
✅ آنها میگویند: «بیا ببینیم چطور میتونیم حلش کنیم.»
🧭 این طرز فکر تو را به نیرویی برای تغییر مثبت تبدیل میکند.
مالکیت، پایه و اساس رهبری است.
و دوباره تأکید میکنم:
مالکیت به این معنا نیست که خودت همه چیز را تعمیر کنی.
بلکه یعنی دیگران را هم همراه کنی، به آنها قدرت بدهی،
و نشان دهی که آنها هم میتوانند بخشی از تغییر مثبت باشند. 💪
🎯 الگوریتم Token Bucket
🚦 چرا Rate Limiting مهم است؟
هر API یا سیستم، محدودیتهایی دارد.
اگر هیچ کنترلی نباشد، ممکن است یک کاربر با ارسال درخواستهای بیش از حد، سرور را از کار بیندازد.
✅ Rate Limiting
این مشکل را حل میکند. با کنترل تعداد درخواستهایی که در بازهی زمانی مشخص اجازهی عبور دارند.
الگوریتمهای متعددی برای این کار وجود دارند:
📍 Fixed Window
📍 Sliding Window
📍 Leaky Bucket
📍 Token Bucket
در میان آنها، Token Bucket یکی از پرکاربردترینهاست،
چون تعادل خوبی بین کنترل پایدار ترافیک و امکان ارسال «Burst»های کوتاه از درخواستها ایجاد میکند.
💡 Token Bucket چیست؟
فرض کنید یک سطل (bucket) دارید که داخل آن توکنها ریخته میشوند:
🪙 توکنها با نرخ ثابتی اضافه میشوند (مثلاً ۵ توکن در هر ثانیه)
هر درخواست برای عبور، باید یک توکن مصرف کند
اگر توکن در دسترس باشد → ✅ درخواست مجاز است
اگر سطل خالی باشد → ❌ درخواست رد (یا معلق) میشود
سطل ظرفیت محدودی دارد، پس تعداد توکنها نمیتواند بینهایت زیاد شود
🔁 نتیجه؟
این روش اجازه میدهد سیستم برای مدت کوتاهی درخواستهای بیشتری بپذیرد (burst)،
اما در بازهی بلندمدت، نرخ کلی همچنان کنترلشده باقی بماند.
🧮 فرمول ریاضی پشت Token Bucket
🪣 C = ظرفیت سطل
⚡️ R = نرخ پر شدن (تعداد توکن در هر ثانیه)
⏱️ T = مدتزمان سپریشده از آخرین پر شدن
در هر لحظه، تعداد توکنها برابر است با:
tokens = min(C, tokens + R * T)
وقتی درخواستی وارد میشود:
if tokens > 0 → allow and tokens -= 1
else → reject
📊 مثال اجرا
• ظرفیت سطل = 10
• نرخ پر شدن = 1 توکن در ثانیه
🕒 زمانبندی رخدادها:
• در زمان 0s → سطل پر است (10 توکن)
• کاربر 5 درخواست فوری میفرستد → 5 توکن باقی میماند
• پس از 5 ثانیه → 5 توکن جدید اضافه میشود → سطل دوباره پر (10 توکن)
• کاربر 15 درخواست میفرستد → فقط 10 درخواست مجاز، 5 درخواست رد میشوند
الگوریتم Token Bucket اجازهی ارسال ناگهانی درخواستها (burst) را تا سقف ظرفیت سطل میدهد،
اما در بلندمدت، نرخ کلی ارسال درخواستها را محدود نگه میدارد.
📌مزایا و معایب
✅️مزایا:
🔹️ امکان ارسال ناگهانی چند درخواست (burst) را فراهم میکند، ولی میانگین نرخ را محدود نگه میدارد.
🔹️ باعث شکلدهی روانتر به ترافیک میشود (smooth traffic shaping).
🔹️ بهطور گسترده در شبکهها و APIها استفاده میشود.
❌️معایب:
🔹️کمی پیچیدهتر از روش Fixed Window Counter است.
🔹️در سیستمهای توزیعشده، نیاز به همگامسازی دقیق دارد.
مثال کد :
using System;
public class TokenBucket
{
private readonly int _capacity; // حداکثر تعداد توکنها
private readonly double _refillRate; // نرخ پر شدن (توکن در هر ثانیه)
private double _tokens; // تعداد توکنهای فعلی
private DateTime _lastRefill; // آخرین زمان پر شدن
public TokenBucket(int capacity, double refillRate)
{
_capacity = capacity;
_refillRate = refillRate;
_tokens = capacity;
_lastRefill = DateTime.UtcNow;
}
private void Refill()
{
var now = DateTime.UtcNow;
var elapsedSeconds = (now - _lastRefill).TotalSeconds;
var addedTokens = elapsedSeconds * _refillRate;
if (addedTokens >= 1)
{
_tokens = Math.Min(_capacity, _tokens + addedTokens);
_lastRefill = now;
}
}
public bool AllowRequest()
{
Refill();
if (_tokens >= 1)
{
_tokens -= 1;
return true;
}
return false;
}
}
public class Program
{
public static void Main()
{
var bucket = new TokenBucket(10, 1); // 10 توکن، پر شدن 1 توکن در هر ثانیه
// شبیهسازی درخواستها در بازههای 200 میلیثانیه
var timer = new System.Timers.Timer(200);
timer.Elapsed += (s, e) =>
{
Console.WriteLine($"Request allowed? {bucket.AllowRequest()}");
};
timer.Start();
Console.WriteLine("Press any key to stop...");
Console.ReadKey();
}
}
🚀 کاربردهای واقعی Token Bucket Algorithm
🔸 دروازههای API مثل AWS API Gateway، Nginx، Envoy از نسخههای مختلف این الگوریتم برای کنترل نرخ درخواستها استفاده میکنند.
🔸 روترهای شبکه (Network Routers) برای Traffic Shaping یا همون تنظیم و کنترل جریان ترافیک به کار میره تا از شلوغی شبکه جلوگیری بشه.
🔸 صفهای پیام (Message Queues) برای جلوگیری از بار بیشازحد روی مصرفکنندهها (Consumers) استفاده میشه.
⚖️ مقایسه با سایر روشها
🪟 Fixed Window Counter →
سادهتره، ولی در مرز بازهها ممکنه رفتار غیرمنصفانه نشون بده و اجازهی Burst زیاد بده.
💧 Leaky Bucket →
نرخ خروج داده رو ثابت نگه میداره، اما انعطافپذیری کمتری برای Burst داره.
🎯 Token Bucket →
ترکیبیه از نرخ ثابت و پشتیبانی از Burst کوتاهمدت — یعنی بهترین تعادل برای کنترل ترافیک در APIها.
🏁 نتیجهگیری
🔹 الگوریتم Token Bucket یکی از کاربردیترین و مؤثرترین روشها برای پیادهسازی Rate Limiting در سیستمهاست.
🔹 پیادهسازی اون سادهه، از ترافیک ناگهانی پشتیبانی میکنه و استفادهی منصفانه از منابع رو تضمین میکنه.
🔹 اگر در حال ساخت API یا سیستم توزیعشده هستی،
حتماً این الگوریتم باید یکی از گزینههای اصلی تو باشه.
🔖هشتگها:
#TokenBucket #RateLimiting #SystemDesign #API #Networking
Forwarded from tech-afternoon (Amin Mesbahi)
این مطلب صرفا نظر و تجربه شخصیه؛ نسخه جهانشمول یا خطکش نیست. تجربهی بیش از دو دهه تعامل و دقت در رفتارها و مسیر رشد آدمها از نگاه یک نفر از ۸ میلیارد جمعیت زمین است! قطعا میشه متون دقیقتر، کاملتر و موشکافانهتری هم نوشت؛ ولی شاید مرورش خالی از لطف نباشه...
لیست معضلات رفتاری، فنی، اخلاقی و تیمی خیلی بلند و مفصله. اما بعضی ویژگیها، نهفقط مشکل هستن، بلکه مانع یادگیری و ریشهی معضلات دیگه هم میشن. من قبل از نوشتن این مطلب، سعی کردم رفتارها و خصوصیتهایی که در خودم «تصور» میکنم بهبود دادم رو مرور کنم، ببینم اگر چه خصوصتی داشتم، مانع جدی برای بهبود میشد؟! بعد این لیست رو اینقدر مرور کردم که چکیدهای از رذائل دربیارم که ریشه مشکلات باشن! به نظرم اونهایی واقعاً «افتضاحترین» هستن که ترکیب خطرناکی از این ۳ ویژگی رو دارن:
۱. نداشتن صداقت و اخلاق حرفهای
یادمون نره: بدترین برنامهنویس، کسی نیست که اشتباه میکنه؛ کسیه که اشتباهش رو پنهان میکنه.
- وانمود میکنه چیزی رو بلده ولی بلد نیست
- دروغ میگه که پیشرفت پروژه خوبه، درحالیکه نیست (green shifting)
- عامدانه code review تقلبی میده؛ فقط یه ابزار آنالیز خودکار باز کرده
- باگها رو قایم میکنه
۲. بیسؤالی، تعصب، توقف رشد
بزرگترین ریسک صنعت ما توقف یادگیریه؛ نه نابلدی!
🧠 کسی که سوال نداره، انگار دیگه دنبال بهتر شدن نیست.
🔒 کسی که تعصب داره (فقط فلان زبان، فقط فلان ابزار)، راه اصلاح رو به خودش بسته؛ شاید فکر کنه داره یاد میگیره؛ ولی داره مهارتش در یاد نگرفتن و توجیه نادانیاش رو تقویت میکنه.
🙈 کسی که اشتباه میکنه، ولی فکر میکنه تقصیر دنیاست، یعنی از دورِ یادگیری خارج شده.
فرق کسی که رو به جلو میره و کسی که رو به زواله توی همین چیزاست.
۳. بیمالکیتی و انفعال
"به من بگو چیکار کنم" ممکنه از دهن یه تازهکار قابل قبول باشه. ولی یه مهندس واقعی باید خودش دنبال معنی، مشکل، راهحل، و تبعات کارش باشه.
- فقط همون کاری رو میکنه که دقیقاً بهش گفتن
- هیچ پیشفرضی رو به چالش نمیکشه (تفکر نقادانه نداره اساسا)
- تغییرات رو با مقاومت پاسخ میده (فناوری، فرآیند، ابزار)
- کارش رو فقط "تا اینجا وظیفهم بود" میبینه
ریشه همهٔ اینها، نداشتن principle (به فارسی پرنسیپ گفته میشه). یعنی کسی که هیچ چارچوب فکری و اخلاقی برای خودش نساخته. درسته که میشه چارچوب بد هم داشت ولی این کلمه در ذاتش بار مثبت اخلاقی داره. کسی که principle نداره نه از خودش نمیپرسه:
«این رفتار درسته؟»
«چرا دارم این کار رو اینجوری انجام میدم؟»
«اصلاً من دارم رشد میکنم یا درجا میزنم؟»
«آیا آدمها از تعامل با من خوشحالن؟ آیا مفیدم؟ چجوری بهتر بشم؟»
یه آدم فاقد principle، بر اساس منفعت لحظهای رفتار میکنه. یه بار پنهان میکنه، یه بار تقلب میکنه، یه بار مقاومت در برابر حرف صحیح، یه بار انفعاله... چون "راهنمای درونی" نداره.
🤝 و آخرش اینه:
- میشه چیزی رو بلد نبود، ولی یاد گرفت، «سوال خوب داشت»
- میشه همتیمی خوبی نبود، ولی مهارت کار تیمی رو تقویت کرد
- میشه اشتباه کرد، ولی پنهانش نکرد، دنبال راهحل گشت، مقاومت و فرافکنی نکرد و دیگه تکرار نکرد
- میشه بهترین نبود، بهترین جا نبود؛ ولی با ساکن و منفعل نبودن، جای بهتری قرار گرفت، محیط بهتری ساخت...
البته بعضی از این رفتارها، در واکنش به محیطهای ناسالم یا تیمهای سمی شکل میگیرن؛ برخیشون ریشه در تربیت، کودکی، جامعه و اطرافیان دارن. ولی اینکه ما با چه اصولی رفتار میکنیم، هنوز دست خودمونه.
کامنت کنید؛ شاید کمک کنه فردا کمی بهتر از امروز باشیم 🌱
Please open Telegram to view this post
VIEW IN TELEGRAM
🧠 مقدمهای بر NoSQL
پایگاهدادههای NoSQL (مخفف Not Only SQL) برای مدیریت حجمهای بزرگ دادههای غیرساختیافته (Unstructured) و نیمهساختیافته (Semi-Structured) طراحی شدهاند.
برخلاف پایگاهدادههای رابطهای (Relational Databases) که به ساختار ثابت و جدولهای مشخص متکی هستند، NoSQL مدل دادهای انعطافپذیر ارائه میدهد و از مقیاسپذیری افقی (Horizontal Scaling) پشتیبانی میکند.
به همین دلیل، NoSQL گزینهای ایدهآل برای برنامههای مدرن است که نیاز به کارایی بالا، مقیاسپذیری گسترده و مدیریت مؤثر دادههای متنوع دارند.
🔑 ویژگیهای کلیدی پایگاهدادههای NoSQL
📂 طرح پویا (Dynamic Schema):
امکان تغییر ساختار داده بدون نیاز به مهاجرت یا بازطراحی پایگاهداده.
⚙️ مقیاسپذیری افقی (Horizontal Scalability):
با افزودن نودهای جدید به خوشه، ظرفیت ذخیرهسازی و توان پردازشی افزایش مییابد و بار کاری بین چند سرور توزیع میشود.
🧾 مبتنی بر سند (Document-Based):
دادهها در قالبهای انعطافپذیر مانند JSON یا BSON ذخیره میشوند (مثل MongoDB).
🔑 مبتنی بر کلید-مقدار (Key-Value):
دادهها بهصورت جفت کلید و مقدار ذخیره میشوند، که دسترسی سریع و سادهای فراهم میکند (مثل Redis).
📊 مبتنی بر ستون (Column-Based):
دادهها در قالب ستونها سازماندهی میشوند، نه ردیفها (مثل Cassandra).
🌐 توزیعشده و در دسترس بالا (Distributed & High Availability):
برای در دسترس بودن مداوم طراحی شدهاند و در صورت خرابی یک نود، دادهها از طریق تکثیر (Replication) روی سایر نودها حفظ میشوند.
🧩 انعطافپذیری بالا:
توسعهدهندگان میتوانند دادهها را بهصورت پویا و با انواع مختلف ساختارها ذخیره و بازیابی کنند.
⚡️ کارایی بالا:
مناسب برای Big Data، تحلیلهای لحظهای (Real-Time Analytics) و برنامههایی با حجم زیاد درخواستها.
⚠️ چالشهای پایگاهدادههای NoSQL
📏 نبود استاندارد مشخص:
هر سیستم NoSQL ساختار و منطق خاص خود را دارد، که انتخاب گزینهی مناسب برای پروژه را دشوارتر میکند.
❌ عدم پشتیبانی کامل از ACID:
برخی از NoSQLها، سازگاری و یکپارچگی داده را بهطور کامل تضمین نمیکنند، که برای سیستمهای حساس به دقت داده میتواند مشکلساز باشد.
🎯 تمرکز محدود:
برای ذخیرهسازی داده عالی هستند، اما در زمینههایی مثل مدیریت تراکنشها به اندازهی پایگاهدادههای رابطهای قوی نیستند.
🔍 پشتیبانی محدود از پرسوجوهای پیچیده:
برای اجرای کوئریهای تحلیلی پیچیده یا گزارشگیریهای سنگین مناسب نیستند.
🧱 بلوغ کمتر نسبت به پایگاهدادههای سنتی:
از نظر امنیت، پایداری و امکانات هنوز به سطح پایگاهدادههای رابطهای نرسیدهاند.
⚙️ پیچیدگی در مدیریت:
نگهداری و مدیریت پایگاهدادههای NoSQL در مقیاس بزرگ میتواند دشوارتر از پایگاهدادههای رابطهای باشد.
💻 ابزارهای گرافیکی محدود:
هرچند برخی ابزارها مثل MongoDB Compass وجود دارند، اما بسیاری از NoSQLها ابزارهای تصویری قوی و کاربرپسند ندارند.
⚔️ SQL vs NoSQL
🗂 مدل داده: ساختیافته و جدولی
📈 مقیاسپذیری: عمودی (Vertical Scaling)
🏗 ساختار (Schema): از قبل تعریفشده
✅ پشتیبانی ACID: قوی
🎯 مناسب برای: برنامههای تراکنشی
💻 نمونهها: MySQL، PostgreSQL، Oracle
🗂 مدل داده: انعطافپذیر (سند، کلید-مقدار، گراف)
📈 مقیاسپذیری: افقی (Horizontal Scaling)
🏗 ساختار (Schema): پویا و بدون ساختار ثابت
✅ پشتیبانی ACID: محدود یا سازگاری تدریجی (Eventual Consistency)
🎯 مناسب برای: دادههای حجیم (Big Data)، تحلیلهای لحظهای
💻 نمونهها: MongoDB، Cassandra، Redis
MongoDB (مبتنی بر سند) → مدیریت محتوا، کاتالوگ محصولات
Redis (کلید-مقدار) → کشینگ، تحلیلهای لحظهای، ذخیرهسازی نشستها
Cassandra (مبتنی بر ستونها) → دادههای حجیم، سیستمهای با دسترسی بالا
Neo4j (گراف) → شناسایی تقلب، شبکههای اجتماعی
📊 برنامههای Big Data: ذخیره و پردازش مؤثر حجمهای بسیار زیاد دادههای غیرساختیافته و نیمهساختیافته
⏱️ تحلیلهای لحظهای (Real-Time Analytics): پشتیبانی از کوئریهای سریع و تحلیل داده برای موتورهای پیشنهاددهنده یا شناسایی تقلب
🌐 برنامههای وب مقیاسپذیر: مدیریت کاربران زیاد و ترافیک بالا با مقیاسپذیری افقی در بین سرورها
🔄 ذخیرهسازی داده انعطافپذیر: مدیریت فرمتهای مختلف داده (JSON، کلید-مقدار، اسناد، گراف) بدون نیاز به ساختار سخت و ثابت
SQL (پایگاهداده رابطهای)
🗂 مدل داده: ساختیافته و جدولی
📈 مقیاسپذیری: عمودی (Vertical Scaling)
🏗 ساختار (Schema): از قبل تعریفشده
✅ پشتیبانی ACID: قوی
🎯 مناسب برای: برنامههای تراکنشی
💻 نمونهها: MySQL، PostgreSQL، Oracle
NoSQL (پایگاهداده غیررابطهای)
🗂 مدل داده: انعطافپذیر (سند، کلید-مقدار، گراف)
📈 مقیاسپذیری: افقی (Horizontal Scaling)
🏗 ساختار (Schema): پویا و بدون ساختار ثابت
✅ پشتیبانی ACID: محدود یا سازگاری تدریجی (Eventual Consistency)
🎯 مناسب برای: دادههای حجیم (Big Data)، تحلیلهای لحظهای
💻 نمونهها: MongoDB، Cassandra، Redis
🗂 پایگاهدادههای محبوب NoSQL و کاربرد آنها
MongoDB (مبتنی بر سند) → مدیریت محتوا، کاتالوگ محصولات
Redis (کلید-مقدار) → کشینگ، تحلیلهای لحظهای، ذخیرهسازی نشستها
Cassandra (مبتنی بر ستونها) → دادههای حجیم، سیستمهای با دسترسی بالا
Neo4j (گراف) → شناسایی تقلب، شبکههای اجتماعی
💡 کاربردهای NoSQL
📊 برنامههای Big Data: ذخیره و پردازش مؤثر حجمهای بسیار زیاد دادههای غیرساختیافته و نیمهساختیافته
⏱️ تحلیلهای لحظهای (Real-Time Analytics): پشتیبانی از کوئریهای سریع و تحلیل داده برای موتورهای پیشنهاددهنده یا شناسایی تقلب
🌐 برنامههای وب مقیاسپذیر: مدیریت کاربران زیاد و ترافیک بالا با مقیاسپذیری افقی در بین سرورها
🔄 ذخیرهسازی داده انعطافپذیر: مدیریت فرمتهای مختلف داده (JSON، کلید-مقدار، اسناد، گراف) بدون نیاز به ساختار سخت و ثابت
🔖هشتگها:
#NoSQL #SQL #Database #DatabaseDesign
🌳 ساختار داده Trie
ساختار داده Trie (Trie Data Structure)، که به آن درخت پیشوندی (Prefix Tree) نیز گفته میشود، یک ساختار داده شبیه درخت است که برای بازیابی سریع جفتهای کلید-مقدار استفاده میشود.
این ساختار معمولاً برای پیادهسازی فرهنگلغتها و قابلیت Autocomplete به کار میرود و جزئی حیاتی در بسیاری از الگوریتمهای جستجو محسوب میشود.
⚡️ ویژگیهای ساختار داده Trie
• هر Trie دارای یک گره ریشه خالی است که لینکها یا ارجاعات به سایر گرهها دارد.
• هر گره نمایانگر یک رشته است و هر یال (Edge) نمایانگر یک کاراکتر میباشد.
• هر گره شامل یک هشمپ (HashMap) یا یک آرایه از اشارهگرها است؛ هر شاخص نمایانگر یک کاراکتر بوده و یک علامت (Flag) مشخص میکند که آیا رشته در گره جاری پایان یافته است یا خیر.
• هر مسیر از ریشه تا هر گره، یک کلمه یا رشته را نمایندگی میکند.
⚔️ مقایسه Trie و Hash Table
ساختار دادهای Trie برای ذخیرهسازی و بازیابی دادهها استفاده میشود و همان عملیاتها میتوانند با استفاده از ساختار دادهای دیگری مانند Hash Table نیز انجام شوند، اما ساختار Trie این عملیاتها را به شکل مؤثرتری انجام میدهد. علاوه بر این، ساختار Trie میتواند برای جستجوی مبتنی بر پیشوند و بازدید مرتب از همه کلمات استفاده شود. بنابراین Trie مزایای هر دو را دارد: هم Hash Table و هم درخت جستجوی دودویی خودمتعادل.
🔹️میتوانیم به شکل مؤثر جستجوی پیشوندی (یا autocomplete) را با Trie انجام دهیم.
🔹️میتوانیم به راحتی تمام کلمات را به ترتیب الفبایی چاپ کنیم که در Hashing به آسانی ممکن نیست.
🔹️در ساختار Trie، هیچ سربار مربوط به توابع هش وجود ندارد.
🔹️جستجوی یک رشته حتی در مجموعه بزرگی از رشتهها در ساختار Trie میتواند با پیچیدگی زمانی O(L) انجام شود، جایی که L طول کلید ورودی است.
🔹️نیاز به فضای حافظه اضافی برای ذخیره کلمات دارد و این فضا ممکن است برای لیستهای طولانی کلمات و/یا کلمات طولانی بسیار زیاد شود.
🌳 طرز کار ساختار داده Trie
ساختار داده Trie میتواند شامل هر تعداد کاراکتر باشد، از جمله حروف الفبا، اعداد و کاراکترهای خاص.
اما در این مطلب، ما تنها رشتههای شامل حروف a تا z را بررسی میکنیم.
بنابراین، هر گره تنها به 26 اشارهگر نیاز دارد، جایی که شاخص 0 نمایانگر 'a' و شاخص 25 نمایانگر 'z' است.
🔹 مثال ذخیرهسازی کلمات "and" و "ant"
زمانی که کلمات "and" و "ant" در Trie ذخیره شوند، ساختار به صورت زیر خواهد بود:
• مسیر مشترک از ریشه: a → n
• شاخه جداگانه برای پایان کلمات:
• d برای "and"
• t برای "ant"
این ویژگی باعث میشود که گرههای مشترک بهینه شوند و جستجو، درج و حذف کلمات با کارایی بالا انجام گیرد.
ساختار داده Trie میتواند شامل هر تعداد کاراکتر باشد، از جمله حروف الفبا، اعداد و کاراکترهای خاص.
اما در این مطلب، ما تنها رشتههای شامل حروف a تا z را بررسی میکنیم.
بنابراین، هر گره تنها به 26 اشارهگر نیاز دارد، جایی که شاخص 0 نمایانگر 'a' و شاخص 25 نمایانگر 'z' است.
🔹 مثال ذخیرهسازی کلمات "and" و "ant"
زمانی که کلمات "and" و "ant" در Trie ذخیره شوند، ساختار به صورت زیر خواهد بود:
• مسیر مشترک از ریشه: a → n
• شاخه جداگانه برای پایان کلمات:
• d برای "and"
• t برای "ant"
این ویژگی باعث میشود که گرههای مشترک بهینه شوند و جستجو، درج و حذف کلمات با کارایی بالا انجام گیرد.
⚡️ کاربردهای ساختار داده Trie
برای پیشنهاد دادن کلمات هنگام تایپ در باکس جستجو استفاده میشود. Trie به پیادهسازی این قابلیت کمک میکند.
اگر کلمه تایپشده در دیکشنری موجود نباشد، پیشنهاداتی بر اساس تایپ کاربر ارائه میشود.
🔹 فرآیند ۳ مرحلهای:
• بررسی وجود کلمه در دیکشنری
• تولید پیشنهادهای ممکن
مرتبسازی پیشنهادات با اولویتبندی
این الگوریتم دیکشنری را ذخیره میکند و الگوریتم جستجوی کلمات را ساده میکند و لیست کلمات معتبر را برای پیشنهاد فراهم میآورد.
در شبکهها و روترهای IP برای بهینهسازی مسیرهای شبکه استفاده میشود. این الگوریتم با ماسکبندی متوالی، زمان جستجو را به O(n) محدود میکند، جایی که n طول آدرس URL بر حسب بیت است.
💡 برای افزایش سرعت جستجو، نسخههای Multiple Bit Trie توسعه داده شدند تا جستجوی چند بیت همزمان را سریعتر انجام دهند.
• مصرف بالای حافظه برای ذخیره تمامی رشتهها
• هر گره دارای تعداد زیادی اشارهگر است که برابر با تعداد حروف است
• در مقایسه با جدول هش کارآمد که زمان جستجوی O(1) دارد، Trie کندتر است (O(l) که l طول رشته است).
1️⃣ ویژگی Autocomplete:
برای پیشنهاد دادن کلمات هنگام تایپ در باکس جستجو استفاده میشود. Trie به پیادهسازی این قابلیت کمک میکند.
2️⃣ Spell Checker (تصحیح املایی):
اگر کلمه تایپشده در دیکشنری موجود نباشد، پیشنهاداتی بر اساس تایپ کاربر ارائه میشود.
🔹 فرآیند ۳ مرحلهای:
• بررسی وجود کلمه در دیکشنری
• تولید پیشنهادهای ممکن
مرتبسازی پیشنهادات با اولویتبندی
این الگوریتم دیکشنری را ذخیره میکند و الگوریتم جستجوی کلمات را ساده میکند و لیست کلمات معتبر را برای پیشنهاد فراهم میآورد.
3️⃣ Longest Prefix Matching (حداکثر طول پیشوند):
در شبکهها و روترهای IP برای بهینهسازی مسیرهای شبکه استفاده میشود. این الگوریتم با ماسکبندی متوالی، زمان جستجو را به O(n) محدود میکند، جایی که n طول آدرس URL بر حسب بیت است.
💡 برای افزایش سرعت جستجو، نسخههای Multiple Bit Trie توسعه داده شدند تا جستجوی چند بیت همزمان را سریعتر انجام دهند.
⚠️ محدودیتهای ساختار داده Trie
• مصرف بالای حافظه برای ذخیره تمامی رشتهها
• هر گره دارای تعداد زیادی اشارهگر است که برابر با تعداد حروف است
• در مقایسه با جدول هش کارآمد که زمان جستجوی O(1) دارد، Trie کندتر است (O(l) که l طول رشته است).
🔖هشتگها:
#Trie #DataStructure #PrefixMatching #Algorithms
🌐 چگونه با NET. یک کوتاهکنندهٔ لینک بسازیم؟
یک کوتاهکنندهٔ URL ابزاری ساده اما قدرتمند است که لینکهای طولانی را به نسخههای کوتاهتر و قابلمدیریتتری تبدیل میکند. این ابزار بهویژه در پلتفرمهایی که محدودیت کاراکتر دارند یا برای بهبود تجربهٔ کاربری و کاهش شلوغی لینکها بسیار مفید است. دو نمونهٔ محبوب از کوتاهکنندههای لینک عبارتاند از Bitly و TinyURL. طراحی چنین سیستمی چالشی جالب است که مسائلی سرگرمکننده برای حل کردن دارد.
اما چطور میتوان یک کوتاهکنندهٔ URL را با NET. ساخت؟
⚙️ عملکردهای اصلی در یک URL Shortener
یک کوتاهکنندهٔ لینک معمولاً دو قابلیت اصلی دارد:
1️⃣ تولید یک کد منحصربهفرد برای هر URL
2️⃣ هدایت (Redirect) کاربر به آدرس اصلی هنگام دسترسی به لینک کوتاهشده
🧩 طراحی سیستم کوتاهکنندهٔ لینک (URL Shortener System Design)
در سطح بالا، سیستم ما شامل دو endpoint است:
• یکی برای کوتاه کردن URLهای بلند
• دیگری برای Redirect کاربران بر اساس لینک کوتاهشده
در این مثال، لینکهای کوتاهشده در یک پایگاه داده PostgreSQL ذخیره میشوند.
برای بهبود عملکرد خواندن (Read Performance)، میتوان از کش توزیعشدهای مانند Redis نیز در سیستم استفاده کرد. ⚡️
🔢 تولید کد منحصربهفرد برای لینکها
ابتدا باید اطمینان حاصل کنیم که سیستم قادر به تولید تعداد زیادی لینک کوتاه است.
برای این منظور، به هر URL بلند یک کد منحصربهفرد اختصاص میدهیم و از آن برای ساخت لینک کوتاه استفاده میکنیم.
طول این کد و مجموعهٔ کاراکترهایی که از آن استفاده میکنیم، تعیینکنندهٔ تعداد لینکهای کوتاهی است که سیستم میتواند تولید کند.
🎲 استراتژی تولید کد تصادفی
ما از استراتژی تولید کد تصادفی (Random Code Generation) استفاده خواهیم کرد.
این روش پیادهسازی سادهای دارد و نرخ برخورد (Collision) آن قابلقبول است.
البته در ازای این سادگی، افزایش اندک در زمان تأخیر (Latency) را بهعنوان معاوضه خواهیم داشت.
بااینحال، در ادامه گزینههای دیگری را نیز برای بهینهسازی بررسی خواهیم کرد. 🔍
🧱 مدل داده (Data Model) در ساخت URL Shortener با NET.
برای شروع، باید مشخص کنیم چه دادههایی را در پایگاه داده ذخیره خواهیم کرد.
مدل دادهای که نیاز داریم بسیار ساده است. ما یک کلاس ShortenedUrl داریم که نمایانگر لینکهایی است که در سیستم ذخیره میشوند:
public class ShortenedUrl
{
public Guid Id { get; set; }
public string LongUrl { get; set; } = string.Empty;
public string ShortUrl { get; set; } = string.Empty;
public string Code { get; set; } = string.Empty;
public DateTime CreatedOnUtc { get; set; }
}
🔍 این کلاس شامل ویژگیهای زیر است:
• LongUrl: آدرس اصلی (بلند)
• ShortUrl: آدرس کوتاهشده
• Code: کد منحصربهفردی که نمایانگر لینک کوتاهشده است
• Id و CreatedOnUtc نیز برای اهداف دیتابیس و ردیابی (tracking) استفاده میشوند.
کاربران با ارسال مقدار Code به سیستم ما، باعث میشوند برنامه بهدنبال LongUrl متناظر بگردد و آنها را به آدرس اصلی هدایت کند. 🚀
🧩 پیکربندی دیتابیس با Entity Framework Core
برای مدیریت ارتباط با دیتابیس، ما یک کلاس ApplicationDbContext ایجاد میکنیم.
این کلاس وظیفهٔ تنظیم موجودیتها (Entities) و پیکربندی context پایگاه داده را برعهده دارد.
در این مرحله دو کار برای بهبود عملکرد انجام میدهیم:
1️⃣ تعیین حداکثر طول فیلد Code با استفاده از HasMaxLength
2️⃣ تعریف یک ایندکس یکتا (Unique Index) روی ستون Code
این ایندکس یکتا مانع از بروز تکرار در مقدار Code میشود،
بنابراین هیچ دو لینک کوتاهی در دیتابیس مقدار مشابهی نخواهند داشت.
همچنین محدود کردن طول رشته باعث صرفهجویی در فضای ذخیرهسازی میشود و برای ایندکسگذاری ستونهای متنی در برخی دیتابیسها ضروری است.
⚠️ نکته: برخی از دیتابیسها رشتهها را بهصورت غیر حساس به حروف کوچک و بزرگ (case-insensitive) در نظر میگیرند.
این موضوع میتواند تعداد لینکهای قابل تولید را بهشدت کاهش دهد.
بنابراین باید دیتابیس را طوری پیکربندی کنید که Code بهصورت case-sensitive ذخیره و بررسی شود.
🧠 پیادهسازی ApplicationDbContext
public class ApplicationDbContext : DbContext
{
public ApplicationDbContext(DbContextOptions options)
: base(options)
{
}
public DbSet<ShortenedUrl> ShortenedUrls { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<ShortenedUrl>(builder =>
{
builder
.Property(shortenedUrl => shortenedUrl.Code)
.HasMaxLength(ShortLinkSettings.Length);
builder
.HasIndex(shortenedUrl => shortenedUrl.Code)
.IsUnique();
});
}
}
🎯 تولید کد منحصربهفرد (Unique Code Generation) در URL Shortener
یکی از مهمترین بخشهای سیستم کوتاهکننده لینک، تولید کد منحصربهفرد برای هر URL است.
الگوریتمهای مختلفی برای پیادهسازی این بخش وجود دارد، اما هدف ما این است که کدها بهصورت یکنواخت در میان تمام مقادیر ممکن توزیع شوند تا احتمال برخورد (collision) کاهش یابد. ⚖️
⚙️ رویکرد انتخابی ما
در این پیادهسازی از تولید کد تصادفی (Random Unique Code Generator) با استفاده از یک الفبای از پیش تعریفشده (Predefined Alphabet) استفاده میکنیم.
این روش ساده است و احتمال برخورد در آن بسیار پایین است — هرچند راهحلهای بهینهتر و سریعتری هم وجود دارد که بعداً به آنها اشاره خواهیم کرد.
🧩 تعریف تنظیمات کوتاهسازی لینک
ابتدا یک کلاس به نام ShortLinkSettings تعریف میکنیم که شامل دو مقدار ثابت (constant) است:
یکی برای تعیین طول کد کوتاه و دیگری برای الفبایی که قرار است از آن کاراکترها انتخاب شوند.
public static class ShortLinkSettings
{
public const int Length = 7;
public const string Alphabet =
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
}
🔢 این الفبا شامل ۶۲ کاراکتر است (۲۶ حرف بزرگ + ۲۶ حرف کوچک + ۱۰ عدد).
بنابراین، تعداد ترکیبهای ممکن برابر خواهد بود با:
62⁷ = 3,521,614,606,208
یا بهصورت خوانا:
سه تریلیون و پانصد بیست و یک میلیارد و ششصد و چهارده میلیون و ششصد و شش هزار و دویست و هشت ترکیب منحصربهفرد! 😮
این مقدار بهراحتی برای اکثر سیستمهای URL Shortener کافی است.
🧠 پیادهسازی سرویس تولید کد (UrlShorteningService)
در ادامه، یک کلاس به نام UrlShorteningService پیادهسازی میکنیم که وظیفهی تولید کد تصادفی و بررسی یکتایی آن در دیتابیس را برعهده دارد.
public class UrlShorteningService(ApplicationDbContext dbContext)
{
private readonly Random _random = new();
public async Task<string> GenerateUniqueCode()
{
var codeChars = new char[ShortLinkSettings.Length];
const int maxValue = ShortLinkSettings.Alphabet.Length;
while (true)
{
for (var i = 0; i < ShortLinkSettings.Length; i++)
{
var randomIndex = _random.Next(maxValue);
codeChars[i] = ShortLinkSettings.Alphabet[randomIndex];
}
var code = new string(codeChars);
if (!await dbContext.ShortenedUrls.AnyAsync(s => s.Code == code))
{
return code;
}
}
}
}
🔍 در این کد:
برای هر کاراکتر از کد کوتاه، یک مقدار تصادفی از Alphabet انتخاب میشود.
سپس با دیتابیس بررسی میکنیم که آیا این کد قبلاً استفاده شده است یا خیر.
اگر کد منحصربهفرد بود، آن را برمیگردانیم؛ در غیر این صورت، مجدداً تلاش میکنیم.
⚠️ نقاط ضعف و بهبودهای احتمالی
1️⃣ افزایش زمان پاسخ (Latency):
در حال حاضر، هر بار باید با دیتابیس چک کنیم که آیا کد تکراری است یا خیر.
✅ راهحل: میتوان کدهای منحصربهفرد را پیشاپیش در دیتابیس تولید و ذخیره کرد تا در لحظه نیازی به بررسی نباشد.
2️⃣ حلقه بینهایت:
اگر برخوردهای متوالی اتفاق بیفتند، این پیادهسازی تا بینهایت تکرار خواهد شد.
✅ راهحل: بهجای while (true)، از یک تعداد تکرار ثابت استفاده کنید و در صورت تکرار زیاد، Exception پرتاب کنید تا سیستم کنترلشدهتر عمل کند.
🚀 طراحی و پیادهسازی URL Shortener در NET. — بخش نهایی
در این بخش، بعد از آمادهسازی منطق اصلی سیستم، میخواهیم دو Endpoint اصلی را پیادهسازی کنیم:
1️⃣ کوتاهسازی لینک (URL Shortening)
2️⃣ ریدایرکت کاربر به لینک اصلی (URL Redirection)
🔗 کوتاهسازی لینک (URL Shortening)
در ابتدا، با استفاده از یک Minimal API ساده، یک Endpoint برای کوتاهسازی URL ایجاد میکنیم.
این Endpoint یک URL را از کاربر میگیرد، آن را اعتبارسنجی میکند، سپس با کمک سرویس UrlShorteningService، یک کد منحصربهفرد تولید کرده و لینک کوتاهشده را در دیتابیس ذخیره میکند. در نهایت، لینک کوتاهشده به کاربر بازگردانده میشود.
public record ShortenUrlRequest(string Url);
app.MapPost("shorten", async (
ShortenUrlRequest request,
UrlShorteningService urlShorteningService,
ApplicationDbContext dbContext,
HttpContext httpContext) =>
{
if (!Uri.TryCreate(request.Url, UriKind.Absolute, out _))
{
return Results.BadRequest("The specified URL is invalid.");
}
var code = await urlShorteningService.GenerateUniqueCode();
var httpRequest = httpContext.Request;
var shortenedUrl = new ShortenedUrl
{
Id = Guid.NewGuid(),
LongUrl = request.Url,
Code = code,
ShortUrl = $"{httpRequest.Scheme}://{httpRequest.Host}/{code}",
CreatedOnUtc = DateTime.UtcNow
};
dbContext.ShortenedUrls.Add(shortenedUrl);
await dbContext.SaveChangesAsync();
return Results.Ok(shortenedUrl.ShortUrl);
});
📌 نکته:
در اینجا احتمال اندکی از Race Condition وجود دارد — چون ابتدا کد تولید میشود و بعد در دیتابیس ذخیره میگردد. ممکن است دو درخواست همزمان، یک کد مشابه تولید کنند.
اما از آنجا که در دیتابیس Unique Index تعریف کردهایم، جلوی درج مقادیر تکراری گرفته میشود.
🔁 ریدایرکت به URL اصلی (URL Redirection)
در سناریوی دوم، وقتی کاربر روی لینک کوتاه کلیک میکند، سیستم باید او را به لینک اصلی هدایت کند.
برای این کار یک Endpoint دیگر ایجاد میکنیم که کد کوتاهشده را از مسیر (route parameter) میگیرد و در دیتابیس جستوجو میکند.
اگر لینک پیدا شد، کاربر به آدرس اصلی Redirect میشود.
app.MapGet("{code}", async (string code, ApplicationDbContext dbContext) =>
{
var shortenedUrl = await dbContext
.ShortenedUrls
.SingleOrDefaultAsync(s => s.Code == code);
if (shortenedUrl is null)
{
return Results.NotFound();
}
return Results.Redirect(shortenedUrl.LongUrl);
});📨 در صورت موفقیت، پاسخ HTTP با کد وضعیت 302 (Found) بازگردانده میشود که نشاندهندهٔ ریدایرکت موقت است.
⚡️ نقاط قابل بهبود در سیستم کوتاهکننده لینک
اگرچه پیادهسازی فعلی کاملاً قابلاستفاده است، اما میتوان با چند بهبود ساده آن را مقیاسپذیرتر و حرفهایتر کرد:
1️⃣ Caching 🧠
استفاده از Redis برای کش کردن لینکها و کاهش بار دیتابیس.
2️⃣ Horizontal Scaling ⚙️
طراحی سیستم برای مقیاسپذیری افقی و مدیریت ترافیک بالا.
3️⃣ Data Sharding 🧩
تقسیم دادهها بین چند دیتابیس برای بهبود کارایی و توزیع بار.
4️⃣ Analytics 📊
افزودن تحلیلها برای رصد تعداد کلیکها، موقعیت کاربران و نرخ استفاده.
5️⃣ User Accounts 👤
امکان ایجاد حساب کاربری برای مدیریت لینکهای کوتاهشده توسط هر کاربر.
✅ حالا شما یک سیستم کامل URL Shortener با NET. دارید!
میتوانید این پروژه را گسترش دهید و با افزودن قابلیتهای بالا، آن را به یک راهکار مقیاسپذیر و قدرتمند در سطح تولید (Production) تبدیل کنید.
🔖هشتگها:
#URLShortener #SystemDesign
⚖️ متعادلسازی Cross-Cutting Concerns در Clean Architecture
Cross-cutting concerns
جنبههایی از نرمافزار هستند که بر کل برنامه تأثیر میگذارند.
اینها قابلیتهایی در سطح کل برنامهاند که در چندین لایه و بخش تکرار میشوند.
نکتهی کلیدی در مدیریت این دغدغهها این است که باید در یک نقطهی مرکزی متمرکز شوند.
این کار از تکرار کد جلوگیری کرده و اتصال شدید (Tight Coupling) میان اجزای سیستم را کاهش میدهد.
🧩 نمونههایی از Cross-Cutting Concerns
🔐 احراز هویت و مجوزدهی (Authentication & Authorization)
🧠 ثبت وقایع و ردیابی (Logging & Tracing)
🚨 مدیریت استثناها (Exception Handling)
✅ اعتبارسنجی (Validation)
⚡️ ذخیرهسازی در حافظه یا کش (Caching)
🏗 Cross-Cutting Concerns در Clean Architecture
در معماری تمیز، Cross-Cutting Concerns نقش مهمی در حفظ قابلیت نگهداری (Maintainability) و مقیاسپذیری (Scalability) سیستم دارند.
در حالت ایدهآل، این دغدغهها باید جدا از منطق اصلی کسبوکار (Core Business Logic) پیادهسازی شوند.
این کار کاملاً با اصول Clean Architecture همراستا است، چرا که بر تفکیک وظایف (Separation of Concerns) و ماژولار بودن سیستم (Modularity) تأکید دارد.
با این کار، قوانین دامنهی شما تمیز و بدون آلودگی باقی میمانند و معماریتان نیز منعطف و قابل گسترش خواهد بود.
🧱 جایگاه مناسب برای Cross-Cutting Concerns
بهترین محل برای پیادهسازی Cross-Cutting Concerns، لایهی Infrastructure است.
در محیط ASP.NET Core میتوانید از Middlewareها، Decoratorها یا Pipeline Behaviorهای MediatR استفاده کنید.
فرقی نمیکند کدام روش را انتخاب کنید — اصل راهنما این است که این دغدغهها باید جدا از منطق اصلی و در یک نقطهی مرکزی مدیریت شوند.
✳️ مثالهایی از رویکردها
🔹 Middleware ها:
مناسب برای کنترلهای سراسری مانند Logging، Authorization یا Exception Handling.
🔹 Decorator ها:
مناسب برای افزودن رفتارهایی مثل Caching یا Logging به سرویسها بدون تغییر در کد اصلی.
🔹 MediatR Pipeline Behavior:
برای اعمال Validation، Logging یا Caching در سطح درخواستها و دستورها در معماری CQRS.
به این ترتیب، Cross-Cutting Concerns بهصورت ساختیافته و قابل مدیریت در کل سیستم توزیع میشوند،
در حالی که منطق اصلی برنامه از آنها جدا و تمیز باقی میماند.