System Design শেখো
সব কেস স্টাডি

Distributed Unique ID Generator (Snowflake) ডিজাইন

12 মিনিটintermediate
এক নজরে
  • Distributed system-এ auto-increment ID কাজ করে না কারণ একাধিক DB/সার্ভার একই নম্বর জেনারেট করে ফেলতে পারে এবং এটা single point of bottleneck হয়ে যায়।
  • Snowflake ID হলো ৬৪-বিট নম্বর = timestamp + machine ID + sequence, যা একই সাথে unique, roughly time-sortable, এবং কোনো central coordination ছাড়াই জেনারেট হয়।
  • মূল চ্যালেঞ্জ হলো clock skew ও clock going backwards—এগুলো না সামলালে duplicate বা out-of-order ID তৈরি হতে পারে।

প্রতিটা বড় সিস্টেমে একটা মৌলিক দরকার—প্রতিটা row, message, order, বা event-এর জন্য একটা unique ID। single database-এ AUTO_INCREMENT দিয়েই কাজ চলে যায়। কিন্তু যখন তোমার ১০টা database shard আর ৫০টা সার্ভার, তখন? আজ আমরা Snowflake-স্টাইল distributed ID generator ডিজাইন করব।

সহজ উদাহরণ

ভাবো একটা হাসপাতালে একজন রিসেপশনিস্ট সবাইকে সিরিয়াল নম্বর দিচ্ছেন—১, ২, ৩... এক কাউন্টারে দারুণ চলে। এখন ১০টা কাউন্টার খুললে? প্রতিটা যদি নিজে থেকে ১ থেকে শুরু করে, একই নম্বর অনেকজন পেয়ে যাবে। সমাধান: প্রতিটা কাউন্টারকে আলাদা প্রিফিক্স দাও (কাউন্টার-3-এর টোকেন 3-001, 3-002...)। Snowflake ঠিক এভাবেই timestamp + machine ID মিলিয়ে প্রতিটা ID আলাদা রাখে।

১. সমস্যা বোঝা (Requirements)

Functional Requirements:

  • প্রতিটা ID globally unique হতে হবে।
  • ID হবে 64-bit (একটা BIGINT/long-এ ধরে যায়, index-friendly)।
  • ID গুলো roughly time-sortable—অর্থাৎ পরে তৈরি ID আগের ID-র চেয়ে বড়।
  • শুধু সংখ্যা (numeric), যাতে DB index ছোট ও দ্রুত হয়।

Non-functional Requirements:

  • High throughput: সেকেন্ডে হাজার-হাজার ID, ideally কোনো network call ছাড়াই (local generation)।
  • Low latency: ID তৈরি হবে মাইক্রোসেকেন্ডে।
  • High availability: কোনো single point of failure থাকবে না।
  • Scalable: নতুন সার্ভার/node যোগ করা সহজ হবে।

কেন auto-increment distributed-এ চলে না?

  • Collision: প্রতিটা node নিজে increment করলে দুটো node একই ID দিতে পারে।
  • Coordination bottleneck: এক central counter থেকে সবাই নিলে সেটাই bottleneck আর single point of failure।
  • Sharding-এ অসম্ভব: ১০টা shard থাকলে কে কোন রেঞ্জ পাবে তা manage করা ভঙ্গুর।
  • Security/business leak: ক্রমিক ID দেখে প্রতিযোগী আন্দাজ করতে পারে দিনে কত order হচ্ছে।

২. স্কেল আন্দাজ (Estimation)

ধরি peak load: 10,000 ID/সেকেন্ড per machine
machine সংখ্যা: 1024 (machine ID 10 bit = 2^10)

মোট ক্ষমতা: 1024 × প্রতি ms-এ sequence

Snowflake bit layout (64 bit):
  1 bit   → sign (সবসময় 0, positive)
  41 bit  → timestamp (millisecond, custom epoch থেকে)
  10 bit  → machine ID (2^10 = 1024 node)
  12 bit  → sequence (2^12 = 4096 ID প্রতি ms প্রতি machine)

প্রতি machine প্রতি ms: 4096 ID
প্রতি machine প্রতি সেকেন্ড: 4096 × 1000 = 4,096,000 ID
1024 machine মিলে: ~4.1 বিলিয়ন ID/সেকেন্ড  → প্রচুর headroom

41-bit timestamp আয়ু:
  2^41 ms ≈ 2.2 × 10^12 ms ≈ 69 বছর
  custom epoch ব্যবহার করলে (যেমন 2020 থেকে) → 2089 পর্যন্ত চলবে

৩. API ডিজাইন

# একটা নতুন unique ID জেনারেট করা (local, no network)
generateId() -> long

# ID থেকে অংশগুলো বের করা (debug/analytics-এর জন্য)
parseId(id: long) -> {
  timestamp: long,   # epoch থেকে millisecond
  machineId: int,
  sequence: int
}

লক্ষ্য করো—generateId() কোনো network বা DB call করে না, পুরোটা in-process হয়। এটাই Snowflake-এর সবচেয়ে বড় শক্তি।

৪. ডেটা মডেল

Snowflake-এর সৌন্দর্য হলো এর কোনো persistent table লাগে না—ID তৈরি হয় bit manipulation দিয়ে। তবে প্রতিটা node-এর in-memory state ও machine ID assignment থাকে।

FieldBitsমান (range)উদ্দেশ্য
sign1সবসময় 0সংখ্যা positive রাখা
timestamp410 – 2.2T mstime ordering, ~69 বছর
machine ID100 – 1023কোন node বানিয়েছে
sequence120 – 4095একই ms-এ একাধিক ID আলাদা করা

Machine ID কোথা থেকে আসে? ZooKeeper/etcd থেকে startup-এ একটা unique machine ID lease নেওয়া হয় (যাতে দুই node একই machine ID না পায়)। বিকল্পে Kubernetes pod ordinal বা config থেকেও আসতে পারে। এই assignment-এর জন্য একটা ছোট coordination store যথেষ্ট—runtime-এ কোনো call লাগে না।

৫. হাই-লেভেল ডিজাইন

প্রতিটা application server-এর ভেতরেই একটা ID generator library থাকে। flow:

  1. Startup-এ machine ID নাও — ZooKeeper/etcd থেকে unique 10-bit ID lease।
  2. generateId() কল হলে:
    • বর্তমান সময় নাও (currentMillis)।
    • যদি এই ms-এ আগে ID দেওয়া হয়ে থাকে → sequence ১ বাড়াও।
    • যদি sequence 4095 ছাড়িয়ে যায় (এই ms-এ আর জায়গা নেই) → পরের ms পর্যন্ত busy-wait করো।
    • নতুন ms হলে sequence 0 থেকে শুরু।
  3. Bit shift দিয়ে জোড়া লাগাও:
id = ((currentMillis - epoch) << 22)
   | (machineId << 12)
   | sequence

timestamp সবচেয়ে উপরে থাকায় বড় ID = নতুন ID, তাই sort করলেই time order।

৬. গভীরে (Deep Dive)

৬.১ Sequence ও একই ms-এ একাধিক ID

একই millisecond-এ একই machine অনেক ID চাইতে পারে। তাই 12-bit sequence counter—এক ms-এ 4096টা পর্যন্ত আলাদা ID। logic:

  • নতুন request-এর currentMillis == lastMillis হলে: sequence = (sequence + 1) & 0xFFF (12-bit mask)।
  • mask করার পর 0 হয়ে গেলে মানে overflow—তখন পরের millisecond না আসা পর্যন্ত while (now <= lastMillis) now = currentMillis() দিয়ে wait।
  • নতুন ms এলে sequence reset হয়ে 0।

এই পুরো block thread-safe হতে হবে (synchronized/atomic), কারণ একই machine-এ multiple thread একসাথে ID চাইতে পারে।

৬.২ Clock Skew ও Clock Going Backwards

এটাই Snowflake-এর সবচেয়ে নাজুক জায়গা। timestamp source হলো system clock, আর NTP sync বা VM migration-এ ঘড়ি হঠাৎ পিছিয়ে যেতে পারে।

  • যদি currentMillis < lastMillis (ঘড়ি পিছিয়েছে), পুরোনো timestamp দিয়ে ID দিলে আগের কোনো ID-র সাথে collision হতে পারে।
  • নিরাপদ আচরণ: ছোট পিছিয়ে গেলে (যেমন কয়েক ms) ঘড়ি catch up না করা পর্যন্ত wait করো। বড় পিছিয়ে গেলে error throw করে ওই node-কে ID দেওয়া থেকে বিরত রাখো।
সাবধান

কখনো ঘড়ি পিছিয়ে গেলে চুপচাপ পুরোনো বা future timestamp দিয়ে ID দিও না। এতে duplicate ID তৈরি হবে যা data corruption ঘটায়—আর এই বাগ ধরা পড়ে অনেক পরে, production-এ, সবচেয়ে খারাপ সময়ে।

৬.৩ Snowflake vs UUID

বৈশিষ্ট্যUUID (v4)Snowflake
সাইজ128-bit64-bit
Sortable?না (random)হ্যাঁ (time-ordered)
Coordinationলাগে নাmachine ID assignment লাগে
DB indexবড়, random write → fragmentationছোট, sequential → দ্রুত
তথ্য বহননাtimestamp + machine বের করা যায়

UUIDv4 পুরো random—কোনো coordination লাগে না, কিন্তু 128-bit বড় এবং random হওয়ায় B-tree index-এ insert হলে page fragmentation ও খারাপ performance দেয়। Snowflake 64-bit, sequential, তাই index-friendly—তবে machine ID assign করার একটা ছোট দাম দিতে হয়। (নোট: UUIDv7 সম্প্রতি time-ordered হওয়ায় Snowflake-এর কাছাকাছি সুবিধা দেয়।)

৭. বটলনেক ও স্কেলিং

  • Machine ID সীমা (1024): 10 bit-এ মাত্র 1024 node। আরও লাগলে bit reallocate করা যায় (যেমন machine 12-bit, sequence 10-bit)—trade-off বুঝে।
  • Clock dependency: পুরো সিস্টেম system clock-এর উপর নির্ভর; NTP ঠিকমতো configure করা জরুরি (slew mode, step নয়)।
  • Sequence overflow: এক ms-এ 4096-র বেশি ID দরকার হলে busy-wait latency বাড়ায়—তখন একাধিক node-এ load ছড়াও।
  • Centralized বিকল্প: খুব high availability চাইলে এক central generator bottleneck হয়; তাই Snowflake-এর decentralized মডেলই scale-এ জেতে।
  • Ticket server বিকল্প: Flickr-এর মতো একটা/দুটো MySQL ticket server (replace into) দিয়েও ID দেওয়া যায়, কিন্তু সেটা network round-trip যোগ করে—Snowflake local হওয়ায় এগিয়ে।

৮. সারসংক্ষেপ

Snowflake একটা সরল কিন্তু শক্তিশালী আইডিয়া: ৬৪ bit-কে timestamp + machine ID + sequence-এ ভাগ করো, সবচেয়ে উপরে timestamp রাখো যাতে ID sortable হয়, আর machine ID-র জন্য একবারই coordination করো—runtime-এ কখনো নয়। ফলে কোনো network call ছাড়াই unique, sortable, 64-bit ID—প্রায় unlimited throughput-এ।

টিপস

ইন্টারভিউতে interviewer প্রায় নিশ্চিতভাবে জিজ্ঞেস করবেন: "ঘড়ি পিছিয়ে গেলে কী হবে?" আর "UUID-র বদলে এটা কেন?" এই দুটোর পরিষ্কার উত্তর (clock-backwards handling এবং 64-bit + sortable + index-friendly সুবিধা) দিতে পারলেই তুমি বুঝিয়ে দেবে যে তুমি শুধু মুখস্থ করোনি, trade-off বোঝো।

মিনি কুইজ

1. Distributed DB-তে auto-increment ID কেন সমস্যা?

2. Snowflake ID-তে timestamp সবচেয়ে উপরের bit-এ রাখা হয় কেন?

3. একটা node-এ ঘড়ি পিছিয়ে গেলে (clock going backwards) নিরাপদ আচরণ কোনটা?