PostgreSQL-এ UUID বনাম bigint: স্কেলযোগ্য আইডি কীভাবে নির্বাচন করবেন
PostgreSQL-এ UUID বনাম bigint: ইনডেক্স সাইজ, সাজানো ক্রম, শার্ডিং-রেডিনেস तुलना করুন এবং দেখুন কীভাবে আইডি API, ওয়েব ও মোবাইল অ্যাপে প্রবাহিত হয়।

কেন আইডি নির্বাচন মনে হওয়ার চেয়ে বেশি গুরুত্বপূর্ণ
PostgreSQL টেবিলের প্রতিটি রো-কে আবার খুঁজে পেতে একটি স্থিতিশীল উপায় দরকার। সেটাই আইডি করে: এটি রেকর্ডকে অনন্যভাবে চিহ্নিত করে, সাধারণত প্রাইমারি কী হয়, এবং সম্পর্কগুলোর জন্য গ্লু হিসাবে কাজ করে। অন্যান্য টেবলগুলো এটাকে ফরেন কীগুলোর মতো সংরক্ষণ করে, কোয়েরি গুলো এটাতে যোগ দেয়, এবং অ্যাপগুলো এটাকে “সেই কাস্টমার”, “সেই ইনভয়েস” বা “সেই সাপোর্ট টিকিট” হিসেবে ঘোরায়।
আইডি সব জায়গায় দেখা দেয়, তাই নির্বাচন কেবল ডাটাবেসের বিষয় নয়। পরে এটা ইনডেক্স সাইজ, লেখার প্যাটার্ন, কোয়েরি গতি, ক্যাশ হিট রেট এবং এমনকি প্রোডাক্ট কাজগুলো — অ্যানালিটিক্স, ইম্পোর্ট, ডিবাগিং—এও প্রকাশ পায়। এটি URL ও API-তে আপনি কী প্রকাশ করেন এবং মোবাইল অ্যাপে ডেটা নিরাপদে স্টোর ও সিঙ্ক করা কত সহজ হবে তাতেও প্রভাব ফেলে।
বেশিরভাগ দল PostgreSQL-এ UUID বনাম bigint তুলনা করে। সাধারণ কথায় আপনি দুইটির মধ্যে নির্বাচন করছেন:
- bigint: একটি 64-বিট সংখ্যা, প্রায়শই একটি sequence দ্বারা জেনারেট করা (1, 2, 3...).
- UUID: একটি 128-বিট শনাক্তকারী, দেখতে এলোমেলো বা সময়-অনুক্রমিক হতে পারে।
কোনও অপশন সবক্ষেত্রে জয়ী নয়। Bigint সাধারণত ইনডেক্স ও সাজানোর জন্য কম্প্যাক্ট এবং বন্ধুত্বপূর্ণ। UUID গ্লোবালি অনন্য আইডি দরকার হলে, পাবলিক আইডি নিরাপদ রাখতে চাইলে, অথবা আশা করলে যেখানে ডেটা অনেক স্থানে তৈরি হবে (মাল্টিপল সার্ভিস, অফলাইন মোবাইল, ভবিষ্যৎ শার্ডিং) ভালো ফিট।
একটি ব্যবহারযোগ্য নিয়ম: আপনার সিদ্ধান্ত নিন কিভাবে আপনার ডেটা তৈরি ও ভাগ করা হবে, কেবল আজ কীভাবে সংরক্ষণ হচ্ছে তা নয়।
Bigint এবং UUID মূল বিষয়গুলো সহজভাবে
লোকেরা যখন PostgreSQL-এ UUID বনাম bigint তুলনা করে, তারা দুই উপায়ের মধ্যে বেছে নিচ্ছে কীভাবে রো-গুলোকে নামকরণ করা হবে: একটি ছোট কাউন্টার-ধাঁচের সংখ্যা, অথবা একটি বড় গ্লোবালি অনন্য মান।
একটি bigint আইডি হলো 64-বিট ইন্টিজার। PostgreSQL-এ আপনি সাধারণত এটি একটি identity column দিয়ে (বা পুরনো serial প্যাটার্ন দিয়ে) জেনারেট করেন। ডাটাবেস হেডে একটি sequence রাখে এবং প্রতিটি ইনসার্টে পরবর্তী সংখ্যা দেয়। এর মানে আইডি সাধারণত 1, 2, 3, 4... ইত্যাদি হয়। এটা সহজ, পড়তে সুবিধাজনক, এবং টুল ও রিপোর্টে সহায়ক।
একটি UUID (Universally Unique Identifier) 128 বিট। এগুলো প্রায়ই হাইফেনসহ 36 ক্যারেক্টারের মত লেখা হয়, যেমন 550e8400-e29b-41d4-a716-446655440000. সাধারন ধরনগুলো:
- v4: র্যান্ডম UUID। যেকোথাও জেনারেট করতে সহজ, কিন্তু সেগুলো তৈরির সময়ক্রম অনুসারে সাজে না।
- v7: সময়-অনুক্রমিক UUID। এখনও অনন্য, কিন্তু সময়ের সাথে প্রায় বাড়ে এমনভাবে ডিজাইন করা।
স্টোরেজ প্রথম প্রাকটিক্যাল পার্থক্য: bigint 8 বাইট ব্যবহার করে, আর UUID 16 বাইট। এই সাইজের ফারাক ইনডেক্সে প্রকাশ পায় এবং ক্যাশ হিট রেটে প্রভাব ফেলে (ডাটাবেস মেমরিতে কম ইনডেক্স এন্ট্রি ফিট করে)।
এছাড়াও ভাবুন আইডি ডাটাবেসের বাইরে কোথায় আসে। Bigint আইডি URL-এ ছোট থাকে এবং লগ বা সাপোর্ট টিকিটে পড়তে সহজ। UUID গুলো লম্বা এবং টাইপ করা অসুবিধাজনক, কিন্তু অনুমান করা কঠিন এবং ক্লায়েন্টে নিরাপদে জেনারেট করা যায়।
ইনডেক্স সাইজ ও টেবিল ব্লট: কী পরিবর্তিত হয়
bigint এবং UUID-র সবচেয়ে বড় বাস্তব পার্থক্য হল সাইজ। Bigint 8 বাইট; UUID 16 বাইট। এটা ক্ষুদ্র শোনালেও মনে রাখবেন ইনডেক্সগুলো আপনার আইডি বারবার পুনরায় রাখে।
আপনার প্রাইমারি কী ইনডেক্স দ্রুত মেমরিতে থাকতে হবে যাতে দ্রুত লাগে। ছোট ইনডেক্স মানে আরও বেশি অংশ shared buffers ও CPU ক্যাশে ফিট করে, তাই লুকআপ ও জয়েনে ডিস্ক রিড কম লাগে। UUID প্রাইমারি কী হলে সাধারণত একই রো কাউন্টে ইনডেক্স বড় হয়।
মাল্টিপ্লাইয়ার হিসেবে আছে সেকেন্ডারি ইনডেক্সগুলো। PostgreSQL B-tree ইনডেক্সে প্রতিটি সেকেন্ডারি ইনডেক্স এন্ট্রিও প্রাইমারি কী ভ্যালু স্টোর করে (যাতে ডাটাবেস রোটি খুঁজে পায়)। তাই চওড়া আইডি শুধু প্রাইমারি কী ইনডেক্সকে নয়, আপনি যে প্রতিটি অন্যান্য ইনডেক্স যোগ করেন সেগুলোকে বাড়ায়। যদি আপনার তিনটি সেকেন্ডারি ইনডেক্স থাকে, UUID-র অতিরিক্ত 8 বাইট কার্যকরভাবে চার জায়গায়ই প্রদর্শিত হয়।
ফরেন কীগুলো ও জয়েন টেবিলগুলোও প্রভাবিত হয়। যে কোনো টেবিল আপনার আইডি রেফারেন্স করে সেই মানটি তার নিজস্ব রো ও ইনডেক্সে সংরক্ষণ করে। একটি many-to-many জয়েন টেবিল দুইটি ফরেন কী এবং কিছু ওভারহেড থাকায়, কী-ওয়াইড দ্বিগুণ হলে তার ফুটপ্রিন্ট অনেক বাড়তে পারে।
বাস্তবে:
- UUID সাধারণত প্রাইমারি এবং সেকেন্ডারি ইনডেক্স বড় করে, এবং পার্থক্য ইনডেক্স বাড়ানোর সাথে গুণিত হয়।
- বড় ইনডেক্স মানে মেমরি চাপ বেশি এবং লোডের সময় আরও পেজ রিড।
- আপনার কী-কে রেফারেন্স করে যত বেশি টেবল থাকবে (events, logs, join tables), সাইজের পার্থক্য তত বেশি গুরুত্বপূর্ণ হবে।
যদি একটি ইউজার আইডি users, orders, order_items, এবং audit_log-এ εμφανίζεται, একই মান সব টেবিলে স্টোর ও ইনডেক্স করা হয়। একটি চওড়া আইডি বেছে নেওয়া হওয়া কেবল আইডি সিদ্ধান্ত নয়, এটি স্টোরেজ সিদ্ধান্তও।
সাজানোর ক্রম এবং লেখা প্যাটার্ন: সিকোয়েন্সিয়াল বনাম র্যান্ডম আইডি
অধিকাংশ PostgreSQL প্রাইমারি কী B-tree ইনডেক্সে থাকে। B-tree সবচেয়ে ভাল কাজ করে যখন নতুন রো ইনডেক্সের শেষে এসে পড়ে, কারণ ডাটাবেস কম রিশ্যাফলিং-এ যোগ করে কেবল অ্যাপেন্ড করতে পারে।
সিকোয়েন্সিয়াল আইডি: পূর্বানুমেয় এবং স্টোরেজ-সুসঙ্গত
bigint identity বা sequence থাকলে, নতুন আইডি সময়ের সাথে বাড়ে। ইনসার্ট সাধারণত ইনডেক্সের ডানদিকে পড়ে, তাই পেজগুলো প্যাক থাকে, ক্যাশ উষ্ণ থাকে, এবং PostgreSQL অতিরিক্ত কম কাজ করে।
এটা তখনও গুরুত্বপূর্ণ যদি আপনি কখনো ORDER BY id না চালান। রাইট-পাথও প্রতিটি নতুন কীকে ইনডেক্সে সাজানো ক্রমে স্থাপন করতে হবে।
র্যান্ডম UUID: বেশি ছড়িয়ে পড়া, বেশি চূর্ণ
র্যান্ডম UUID (সাধারণত UUIDv4) ইনসার্টগুলো ইনডেক্স জুড়ে ছড়িয়ে দেয়। এতে page splits হওয়ার সম্ভাবনা বাড়ে, যেখানে PostgreSQL নতুন ইনডেক্স পেজ আলোকেট করে এবং এন্ট্রিগুলো স্থানান্তর করে। ফল হলো বেশি write amplification: ইনডেক্সে বেশি বাইট লেখা, বেশি WAL উৎপন্ন, এবং পরে বেশি ব্যাকগ্রাউন্ড কাজ (vacuum এবং bloat ম্যানেজমেন্ট)।
টাইম-অর্ডার UUID এই গল্প বদলে দেয়। সময়-অনুক্রমিক UUID (যেমন UUIDv7-স্টাইল বা অন্যান্য টাইম-ভিত্তিক স্কিম) অনেকটা লোকালিটি পুনঃস্থাপন করে, এখনও 16 বাইট এবং API-তে UUID-এর মতই দেখা যায়।
আপনি এই পার্থক্য সবচেয়ে বেশি অনুভব করবেন যখন আপনার ইনসার্ট রেট উচ্চ, বড় টেবিল মেমরিতে ফিট করে না, এবং একাধিক সেকেন্ডারি ইনডেক্স থাকে। যদি আপনি page split থেকে আসা write latency spike-এর প্রতি সংবেদনশীল হন, হট রাইট টেবিলে সম্পূর্ণ র্যান্ডম আইডি এড়ান।
উদাহরণ: মোবাইল অ্যাপ লগগুলো সারাদিন যে একটি ব্যস্ত events টেবিলে আসে তা সাধারণত সিকোয়েন্সিয়াল কী বা সময়-অনুক্রমিক UUID-এ বেশি মসৃণভাবে চলে তুলনায় সম্পূর্ণ র্যান্ডম UUID-র।
বাস্তবে আপনি যে পারফরম্যান্স অনুভব করবেন
বাস্তব জগতের ধীরগতি সাধারণত “UUID ধীর” বা “bigint দ্রুত” এই সরলীকরণ নয়। এটা হচ্ছে ডাটাবেসকে কী স্পর্শ করতে হচ্ছে যাতে আপনার কোয়েরি উত্তর দেয়।
কোয়েরি প্ল্যান প্রধানত বিবেচনা করে তারা ইনডেক্স স্ক্যান ব্যবহার করতে পারে কিনা ফিল্টারের জন্য, কি দ্রুত কী-তে জয়েন করা যায়, এবং টেবিল শারীরিকভাবে সাজানো আছে কিনা যাতে রেঞ্জ রিডস সস্তায় হয়। একটি bigint প্রাইমারি কী-এ নতুন রো প্রায় বাড়ির ক্রমে আসে, তাই প্রাইমারি কী ইনডেক্স সাধারণত কম্প্যাক্ট এবং লোকালিটি-ফ্রেন্ডলি থাকে। র্যান্ডম UUID-তে ইনসার্ট ইনডেক্স জুড়ে ছড়িয়ে পড়ে, যা বেশি page split এবং অন-ডিস্ক অর্ডারের বিশৃঙ্খলা সৃষ্টি করতে পারে।
রিডগুলোতে অনেক দল প্রথমে এটা লক্ষ্য করে। বড় কী মানে বড় ইনডেক্স, আর বড় ইনডেক্স মানে কম পেজ RAM-এ ফিট করে, তাই ক্যাশ হিট রেট কমে এবং I/O বাড়ে, বিশেষত জয়েন-ভিত্তিক স্ক্রিনগুলোতে যেমন "list orders with customer info"। যদি আপনার ওয়ার্কিং সেট মেমরিতে না ফিট করে, UUID-ভিত্তিক স্কিম আপনাকে তাড়াতাড়ি সেই সীমা অতিক্রম করাতে পারে।
রাইটও বদলে যেতে পারে। র্যান্ডম UUID ইনসার্ট ইনডেক্সে বেশি churn বাড়াতে পারে, যা autovacuum-এ চাপ দেয় এবং ব্যস্ত সময়ে latency spike হিসেবে দেখা যায়।
যদি আপনি PostgreSQL-এ UUID বনাম bigint বেঞ্চমার্ক করেন, সৎভাবে করুন: একই স্কিমা, একই ইনডেক্স, একই fillfactor, এবং যথেষ্ট রো যাতে RAM-পার হয় (10k নয়)। p95 latency ও I/O মাপুন, এবং উষ্ণ ও ঠান্ডা ক্যাশ দুইটাই টেস্ট করুন।
যদি আপনি AppMaster-এ অ্যাপ বানান যার ব্যাকএন্ড PostgreSQL, এটি সাধারণত লিস্ট পেজগুলো ধীর এবং ডাটাবেস লোড ভারী হয়ে ওঠা হিসেবে আগে দেখায়, CPU সমস্যা হওয়ার আগে।
পাবলিক-ফেসিং সিস্টেমে নিরাপত্তা ও ব্যবহারযোগ্যতা
আপনার আইডি যদি ডাটাবেস ছেড়ে URL, API রেসপন্স, সাপোর্ট টিকিট এবং মোবাইল স্ক্রিনে দেখা যায়, তাহলে পছন্দ করা সেফটি ও দৈনন্দিন ব্যবহারযোগ্যতা উভয়ের ওপর প্রভাব ফেলে।
Bigint আইডি মানুষের জন্য সহজ। সংক্ষেপ, ফোনে পড়ার মত, এবং আপনার সাপোর্ট টিম দ্রুত প্যাটার্ন দেখতে পারে যেমন "সব ব্যর্থ অর্ডারগুলো 9,200,000-এর আশেপাশে"। এটা ডিবাগিং দ্রুত করে, বিশেষত লগ বা কাস্টমার স্ক্রিনশট থেকে কাজ করলে।
UUID তখন উপকারী যখন আপনি আইডি পাবলিকভাবে প্রকাশ করেন। একটি UUID অনুমান করা কঠিন, তাই সাধারণ স্ক্র্যাপিং যেমন /users/1, /users/2, /users/3 কাজ করবে না। এটি বাইরের লোকদের জন্য রেকর্ডের সংখ্যা অনুমান করাও কঠিন করে তোলে।
তবে ফাঁদটা হল “অানুমান করা যায় না” মানে “নিরাপদ” এই ধারণা। যদি অথরাইজেশন চেক দুর্বল হয়, ভবিষ্যদ্বাণীমূলক bigint আইডি দ্রুত শোষিত হতে পারে, কিন্তু UUID-ও শেয়ার করা লিংক, লিকেড লগ বা ক্যাশড API রেসপন্স থেকে চুরি হতে পারে। নিরাপত্তা আইডি লুকানোর উপর নয়—অথরাইজেশন চেকের উপর নির্ভর করা উচিত।
প্রায়োগিক পদ্ধতি:
- প্রতি রিড ও রাইটে ownership বা role চেক বাধ্যতামূলক করুন।
- যদি আপনি পাবলিক API-তে আইডি প্রকাশ করেন, UUID বা আলাদা পাবলিক টোকেন ব্যবহার করুন।
- মানুষের-পঠনযোগ্য রেফারেন্স চাইলে, অভ্যন্তরীণভাবে bigint রাখুন অপস জন্য।
- ID-তে সংবেদনশীল মান এনকোড করবেন না (যেমন ইউজার টাইপ)।
উদাহরণ: একটি কাস্টমার পোর্টালে ইনভয়েস আইডি দেখায়। যদি ইনভয়েসগুলো bigint ব্যবহার করে এবং আপনার API শুধু পরীক্ষা করে "invoice exists", কেউ নম্বর গুলি ইটারেট করে অন্যের ইনভয়েস ডাউনলোড করতে পারবে। আগে চেক ঠিক করুন। তারপরে নির্ধারণ করুন UUID পাবলিক ইনভয়েস আইডি হিসেবে ঝুঁকি ও সাপোর্ট লো কমায় কি না।
AppMaster-এর মতো প্ল্যাটফর্মে, যেখানে আইডি জেনারেট হওয়া API ও মোবাইল অ্যাপের মাধ্যমে প্রবাহিত হয়, সবচেয়ে নিরাপদ ডিফল্ট হলো ধারাবাহিক অথরাইজেশন + এমন একটি আইডি ফরম্যাট যা ক্লায়েন্টগুলো নির্ভরযোগ্যভাবে হ্যান্ডল করতে পারে।
API ও মোবাইল অ্যাপে আইডি কিভাবে প্রবাহিত হয়
আপনি যে ডাটাবেস টাইপ বেছে নেন তা ডাটাবেসেই থামে না। এটা সব বাউন্ডারিতে লিক করে: URL, JSON পে-লোড, ক্লায়েন্ট স্টোরেজ, লগ, এবং অ্যানালিটিক্স।
যদি পরে আপনি আইডি টাইপ পরিবর্তন করেন, ভাঙন কখনই "কেবল একটি মাইগ্রেশন" হয় না। ফরেন কীগুলো সব জায়গায় বদলাতে হয়, না শুধু মূল টেবিলে। ORMs এবং কোড জেনারেটর মডেলগুলো রিজেনারেট করতে পারে, কিন্তু ইন্টিগ্রেশনগুলো এখনও পুরনো ফরম্যাট প্রত্যাশা করে। এমনকি একটি সাধারণ GET /users/123 এন্ডপয়েন্ট UUID-তে 36-ক্যারেক্টার হলে জটিল হয়ে ওঠে। ক্যাশ, মেসেজ কিউ, এবং যেকোনও জায়গাও আপডেট করতে হবে যেখানে আইডি ইন্টিজার হিসেবে স্টোর করা ছিল।
API-এর জন্য সবচেয়ে বড় সিদ্ধান্ত হলো ফরম্যাট ও ভ্যালিডেশন। Bigints সংখ্যায় যায়, কিন্তু কিছু সিস্টেম (এবং কিছু ভাষা) বড় মান ফ্লোটিং-পয়েন্ট হিসেবে পার্স করলে precision সমস্যা হতে পারে। UUID স্ট্রিং হিসেবে যায়, যা পার্সিং-এর জন্য নিরাপদ, কিন্তু "প্রায় UUID" কাঁচা ডেটা লগ বা ডাটাবেসে না ঢুকার জন্য সঠিক ভ্যালিডেশন দরকার।
মোবাইলে, আইডি ধারাবাহিকভাবে সিরিয়ালাইজ ও স্টোর হয়: JSON রেসপন্স, লোকাল SQLite, এবং অফলাইনে কিউ যা নেটওয়ার্ক ফিরে না আসা পর্যন্ত অ্যাকশন সংরক্ষণ করে। নিউমেরিক আইডি ছোট, কিন্তু স্ট্রিং UUID সাধারণত opaque টোকেন হিসেবে ব্যবহার করা সহজ। প্রকৃত কষ্ট আসে অসঙ্গতিপূর্ণতা থেকে: এক লেয়ার এটিকে ইন্টিজার হিসেবে স্টোর করে, আরেকটি টেক্সট হিসেবে—ফিরে মিলানো বা তুলনা দুর্বল হয়ে পড়ে।
কিছু নিয়ম যা টিমগুলোকে ঝামেলা থেকে বাঁচায়:
- API-র জন্য একটি canonical রেপ্রেজেন্টেশন বেছে নিন (প্রায়শই স্ট্রিং) এবং সেটি বজায় রাখুন।
- এজে আইডি ভ্যালিডেশন করুন এবং পরিষ্কার 400 ত্রুটি ফিরিয়ে দিন।
- লোকাল ক্যাশ ও অফলাইন কিউতে একই রেপ্রেজেন্টেশন স্টোর করুন।
- সার্ভিসগুলোর মধ্যে লগে আইডি একই ফিল্ড নাম ও ফরম্যাটে লগ করুন।
AppMaster-এর মতো জেনারেটেড স্ট্যাক দিয়ে ওয়েব ও মুবাইল ক্লায়েন্ট বানালে, একটি স্থিতিশীল ID কনট্রাক্ট আরও গুরুত্বপূর্ণ হয় কারণ এটি প্রতিটি জেনারেটেড মডেল ও রিকোয়েস্টের অংশ হয়ে যায়।
শার্ডিং-রেডিনেস এবং বিতরণকৃত সিস্টেম
“Sharding-ready” মূলত মানে আপনি একাধিক স্থানে আইডি তৈরি করতে পারবেন অনায়াসে, এবং পরে ডেটা নোডগুলোর মধ্যে নাড়াচাড়া করলে প্রতিটি ফরেন কী পুনঃলিখন করতে হবে না।
UUID জনপ্রিয় মাল্টি-রিজিয়ন বা মাল্টি-রাইটার সেটআপে কারণ যেকোনো নোড কেন্দ্রীয় sequence ছাড়াই ইউনিক আইডি জেনারেট করতে পারে। এতে সমন্বয় কম লাগে এবং বিভিন্ন রিজিয়নে রাইট গ্রহণ করা এবং পরে ডেটা মার্জ করা সহজ হয়।
Bigint এখনও কাজ করতে পারে, কিন্তু একটি পরিকল্পনা দরকার। সাধারণ অপশনগুলোর মধ্যে আছে শার্ড অনুযায়ী নুমেরিক রেঞ্জ বরাদ্দ (শার্ড 1 ব্যবহার করে 1-1B, শার্ড 2 ব্যবহার করে 1B-2B), আলাদা sequence চালানো শার্ড প্রিফিক্সের সঙ্গে, অথবা Snowflake-স্টাইল আইডি (টাইম-বেসড বিটস + মেশিন/শার্ড বিটস)। এগুলো UUID-এর তুলনায় ইনডেক্স ছোট রাখতে পারে এবং কিছু অর্ডারিং বজায় রাখতে পারে, কিন্তু অপারেশনাল নিয়ম যোগ করে যা আপনাকে বজায় রাখতে হবে।
দৈনন্দিন জীবনে গুরুত্বপূর্ণ ট্রেড-অফ:
- Coordination: UUID প্রায়ই প্রয়োজন নেই; bigint প্রায়ই রেঞ্জ পরিকল্পনা বা জেনারেটর সার্ভিস চায়।
- Collisions: UUID কোলিশন হওয়ার সম্ভাবনা অত্যন্ত কম; bigint তখনই নিরাপদ যখন বরাদ্দের নিয়ম কখনও ওভারল্যাপ না করে।
- Ordering: অনেক bigint স্কিম প্রায় সময়-অর্ডারেড; UUID সাধারণত র্যান্ডম যদি আপনি টাইম-অর্ডার ভ্যারিয়েন্ট ব্যবহার না করেন।
- Complexity: শার্ড করা bigint কেবল তখনই সহজ থাকে যখন দল শৃঙ্খলাবদ্ধ থাকে।
বেশি দলের কাছে “sharding-ready” আসলে “migration-ready” মানে। যদি আপনি একক DB-এ থাকেন আজ, সেই ID বেছে নিন যা আজকার কাজকে সহজ করে। যদি ইতিমধ্যেই আপনি একাধিক রাইটার তৈরি করছেন (উদাহরণস্বরূপ, AppMaster দিয়ে জেনারেটেড API ও মোবাইল অ্যাপ), তখন আগে থেকে নির্ধারণ করুন কিভাবে আইডি সার্ভিসগুলোর মধ্যে তৈরি ও ভ্যালিডেট হবে।
ধাপে ধাপে: সঠিক ID স্ট্রাটেজি নির্বাচন
আপনার অ্যাপের প্রকৃত আকৃতি প্রথমে নির্ধারণ করুন। একক PostgreSQL ডাটাবেস এক রিজিয়নে থাকা সম্পূর্ণ ভিন্ন প্রয়োজন তৈরি করে বনাম একটি মাল্টি-টেন্যান্ট সিস্টেম, এমন একটি সেটআপ যা ভবিষ্যতে রিজিয়ন অনুযায়ী বিভক্ত হতে পারে, অথবা একটি অফলাইন-ফার্স্ট মোবাইল অ্যাপ যা রেকর্ড অফলাইন তৈরি করে পরে সিঙ্ক করবে।
তারপর সৎ হন যে আইডি কোথায় কোথায় প্রদর্শিত হবে। যদি আইডেন্টিফায়ারগুলো শুধু ব্যাকএন্ডে (জব, ইন্টারনাল টুল, অ্যাডমিন প্যানেল) থাকে, সরলতা প্রায়শই জিতবে। যদি আইডি URL, কাস্টমার-শেয়ার করা লগ, সাপোর্ট টিকিট, বা মোবাইল ডীপ লিঙ্কে আসে, তখন পূর্বানুমেয়তা এবং প্রাইভেসি বেশি গুরুত্ব পায়।
অর্ডারিংকে সিদ্ধান্ত হিসেবে নিন, না পরে ভাবুন। যদি আপনি "নতুন প্রথম" ফিড, স্থিতিশীল পেজিনেশন, বা সহজে স্ক্যান করার মত অডিট ট্রেইলের উপর নির্ভর করেন, সিকোয়েন্সিয়াল আইডি (বা সময়-অনুক্রমিক আইডি) অসুবিধা কমায়। যদি অর্ডারিং প্রধান কী না হলে, আপনি PK ভিন্ন রেখে টাইমস্ট্যাম্প অনুযায়ী সাজাতে পারেন।
একটি যৌক্তিক সিদ্ধান্ত প্রবাহ:
- আপনার আর্কিটেকচার শ্রেণীবদ্ধ করুন (single DB, multi-tenant, multi-region, offline-first) এবং আপনি কি ভবিষ্যতে একাধিক সোর্সের ডেটা মার্জ করবেন কি না।
- নির্ধারণ করুন আইডি কি পাবলিক আইডেন্টিফায়ার হবে না কি কেবল অভ্যন্তরীণ।
- আপনার অর্ডারিং ও পেজিনেশন প্রয়োজন কনফার্ম করুন। যদি প্রাকৃতিক ইনসার্শন অর্ডার দরকার, সম্পূর্ণ র্যান্ডম আইডি এড়ান।
- যদি আপনি UUID নেন, উদ্দেশ্যভিত্তিক একটি ভার্সন বেছে নিন: অপরিবর্তনীয়তার জন্য random (v4), বা ইনডেক্স লোকালিটি ভালো রাখার জন্য time-ordered।
- শীঘ্রই কনভেনশন লক করুন: একটি canonical টেক্সট ফর্ম, কেস নিয়ম, ভ্যালিডেশন, এবং প্রতিটি API কীভাবে আইডি রিটার্ন ও গ্রহণ করবে।
উদাহরণ: একটি মোবাইল অ্যাপ অফলাইনে "ড্রাফট অর্ডার" তৈরি করে তারপর পরে সিঙ্ক করে—UUID ডিভাইসে নিরাপদভাবে আইডি জেনারেট করতে দেয়। AppMaster-এর মতো টুলে এটাও সুবিধাজনক কারণ একই আইডি ফরম্যাট ডাটাবেস থেকে API, থেকে ওয়েব ও নেটিভ অ্যাপে কোনও বিশেষ কেসিং ছাড়া প্রবাহিত হয়।
সাধারণ ভুল ও ফাঁদগুলি থেকে সতর্ক থাকুন
অধিকাংশ আইডি বিতর্ক তখনই ভুল হয় যখন মানুষ একটি কারণে আইডি টাইপ নির্বাচন করে, পরে পার্শ্ব প্রতিক্রিয়ায় অবাক হয়ে যায়।
একটি সাধারণ ভুল হলো একটি হট রাইট টেবিলে সম্পূর্ণ র্যান্ডম UUID ব্যবহার করা এবং পরে হতবাক হওয়া কেন ইনসার্ট স্পাইক হয়। র্যান্ডম ভ্যালুগুলো ইনডেক্স জুড়ে ছড়িয়ে পড়ে, যার ফলে page splits বাড়ে এবং লোডের সময় ডাটাবেসে বেশি কাজ হয়। যদি টেবিল রাইট-হেভি হয়, ইনসার্ট লোকালিটি নিয়ে চিন্তা করে সিদ্ধান্ত নিন।
আরেকটি সাধারণ সমস্যা হলো সার্ভিস ও ক্লায়েন্টে আইডি টাইপ মিক্স করা। উদাহরণ: এক সার্ভিস bigint ব্যবহার করে, অন্যটি UUID, এবং আপনার API উভয় নম্বর ও স্ট্রিং আইডি নিয়ে কাজ করে। এটি স্যাবটল বাগ তৈরি করে: JSON পার্সার বড় নম্বর ফ্লোট হিসেবে পড়ে precision হারিয়ে ফেলে, মোবাইল কোড একটি স্ক্রিনে আইডি সংখ্যা হিসেবে ধরে আর অন্য স্ক্রিনে স্ট্রিং—বা ক্যাশিং কী ম্যাচ করে না।
তৃতীয় ফাঁদ হলো “অানুমানযোগ্য আইডি”-কে নিরাপত্তা ভাবা। যদিও আপনি UUID ব্যবহার করেন, সঠিক অথরাইজেশন চেক এখনও প্রয়োজন।
সবশেষে, দলগুলো পরে পরিকল্পনা ছাড়া আইডি টাইপ বদলায়। সবচেয়ে কঠিন অংশ কেবল প্রাইমারি কী নয়, বরং তার সাথে যা সংযুক্ত: ফরেন কী, জয়েন টেবিল, URL, অ্যানালিটিক্স ইভেন্ট, মোবাইল ডীপ লিঙ্ক, এবং ক্লায়েন্টে স্টোর করা অবস্থা।
বেদনা এড়াতে:
- পাবলিক API-র জন্য একটি আইডি টাইপ বেছে নিন এবং সেটি মেনে চলুন।
- ক্লায়েন্টে আইডি opaque স্ট্রিং হিসেবে ধরুন যাতে নিউমেরিক ইজ্যু এড়ানো যায়।
- কখনই ID randomness-কে access control হিসেবে ব্যবহার করবেন না।
- যদি মাইগ্রেট করতে হয়, API ভার্সনিং করুন এবং দীর্ঘ-সময়ের ক্লায়েন্টগুলোর জন্য পরিকল্পনা রাখুন।
AppMaster-এর মতো কোড-জেনারেটিং প্ল্যাটফর্মে নির্মাণ করলে, কনসিস্টেন্সি আরও গুরুত্বপূর্ণ কারণ একই ID টাইপ ডাটাবেস স্কিমা থেকে জেনারেটেড ব্যাকএন্ড এবং ওয়েব ও মোবাইল অ্যাপে প্রবাহিত হয়।
সিদ্ধান্ত নেওয়ার আগে দ্রুত চেকলিস্ট
আপনি ঠেকলে তত্ত্ব দিয়ে শুরু করবেন না—আপনার প্রোডাক্ট এক বছর পর কেমন দেখাবে এবং ওই আইডি কত জায়গায় যাবে তা থেকে শুরু করুন।
জিজ্ঞাসা করুন:
- 12–24 মাসে বড় টেবিলগুলো কত বড় হবে, এবং আপনি কি বছরের ইতিহাস রাখতে চান?
- কি আপনাকে creation time অনুযায়ী আনুমানিক সাজানো ID দরকার সহজ পেজিং ও ডিবাগিং-র জন্য?
- একাধিক সিস্টেম কি একই সময়ে রেকর্ড তৈরি করবে, অফলাইন মোবাইল বা ব্যাকগ্রাউন্ড জব সহ?
- কি আইডি URL, সাপোর্ট টিকিট, এক্সপোর্ট বা স্ক্রিনশট-এ দেখানো হবে?
- কি প্রতিটি ক্লায়েন্ট একইভাবে ID হ্যান্ডল করতে পারবে (web, iOS, Android, স্ক্রিপ্ট), ভ্যালিডেশন ও স্টোরেজ সহ?
তারপর প্লাম্বিং যাচাই করুন। যদি আপনি bigint ব্যবহার করেন, নিশ্চিত করুন প্রতিটি পরিবেশে (বিশেষত লোকাল ডেভ ও ইম্পোর্টে) আইডি জেনারেশনের একটি পরিষ্কার পরিকল্পনা আছে। যদি UUID ব্যবহার করেন, নিশ্চিত করুন API কনট্রাক্ট ও ক্লায়েন্ট মডেল স্ট্রিং আইডি ঠিকমতো হ্যান্ডল করে এবং টিম সেগুলো পড়তে ও তুলনা করতে স্বাচ্ছন্দ্য বোধ করে।
একটি বাস্তব পরীক্ষা: যদি একটি মোবাইল অ্যাপকে অফলাইনে অর্ডার তৈরি করে পরে সিঙ্ক করতে হয়, UUID সাধারণত সমন্বয় কাজ কমায়। যদি আপনার অ্যাপ বেশিরভাগ অনলাইনে থাকে এবং আপনি ছোট, কম্প্যাক্ট ইনডেক্স চান, bigint সাধারণত সহজ।
AppMaster-এ নির্মাণ করলে, আগে থেকে সিদ্ধান্ত নিন যাতে আপনার ডাটাবেস মডেল, API এন্ডপয়েন্ট, এবং মোবাইল ক্লায়েন্টগুলো জেনারেট করার সময় ধারাবাহিক থাকে এবং প্রকল্প বড় হলে সমস্যা না হয়।
একটি বাস্তব উদাহরণ দৃশ্য
একটি ছোট কোম্পানির আছে একটি অভ্যন্তরীণ অপস টুল, একটি কাস্টমার পোর্টাল, এবং ফিল্ড স্টাফের জন্য একটি মোবাইল অ্যাপ। তিনটাই একই PostgreSQL ডাটাবেসে এক API-র মাধ্যমে হিট করে। সারাদিন নতুন রেকর্ড তৈরি হয়: টিকিট, ছবি, স্ট্যাটাস আপডেট, এবং ইনভয়েস।
bigint আইডি থাকলে API পে-লোডগুলো কমপ্যাক্ট ও পড়তে সহজ থাকে:
{ "ticket_id": 4821931, "customer_id": 91244 }
পেজিনেশন স্বাভাবিক লাগে: ?after_id=4821931&limit=50. id দিয়ে সাজালে সাধারণত ক্রিয়েশনের সঙ্গে মেলে, তাই “latest tickets” দ্রুত এবং পূর্বানুমেয়। ডিবাগিংও সহজ: সাপোর্ট “ticket 4821931” চাইলে অধিকাংশ মানুষ টাইপ করে দিতে পারে।
UUID থাকলে পে-লোডগুলো লম্বা হয়:
{ "ticket_id": "3f9b3c0a-7b9c-4bf0-9f9b-2a1b3c5d1d2e" }
যদি আপনি random UUID v4 ব্যবহার করেন, ইনসার্ট ইনডেক্স জুড়ে ছড়িয়ে পড়ে। এর ফলে ইনডেক্স চূর্ণ বেশি এবং দিন-প্রতি ডিবাগিং একটু অগোছালো হতে পারে (কপি/পেস্ট বেশি ব্যবহৃত হয়)। পেজিনেশন প্রায়ই "after id"-এর বদলে কার্সার-স্টাইল টোকেনে চলে যায়।
যদি আপনি time-ordered UUID ব্যবহার করেন, আপনি বেশিরভাগ “নতুন প্রথম” আচরণ রাখেন এবং তবুও পাবলিক URL-এ অনুমান করা কঠিন রাখেন।
বাস্তবে দলগুলো সাধারণত চারটি জিনিস লক্ষ্য করে:
- কতবার মানুষ আইডি টাইপ করে বনাম কপি/পেস্ট করে
sort by idকিsort by createdএর সাথে মেলে- কার্সর পেজিনেশন কতটা পরিষ্কার ও স্থিতিশীল অনুভব হয়
- কিভাবে একটি রেকর্ড লোগ, API কল, ও মোবাইল স্ক্রিন জুড়ে ট্রেস করা যায়
পরবর্তী ধাপ: ডিফল্ট বেছে নিন, টেস্ট করুন, এবং স্ট্যান্ডার্ড করুন
বেশিরভাগ দল আদৌ নিখুঁত উত্তরের খোঁজে আটকে যায় না। আপনাকে নিখুঁত চাই না—একটি ডিফল্ট দরকার যা আজকের প্রোডাক্টে মানায়, এবং দ্রুত প্রমাণ করার উপায় যে পরে এটা সমস্যার সৃষ্টি করবে না।
আপনি স্ট্যান্ডার্ড করতে পারেন এমন নিয়মগুলো:
- আপনি যখন সবচেয়ে ছোট ইনডেক্স, পূর্বানুমেয় অর্ডারিং, এবং সহজ ডিবাগিং চান তখন
bigintব্যবহার করুন। - আপনি যখন আইডি পাবলিকভাবে অনুমান করা কঠিন রাখতে চান, অফলাইন ক্রিয়েশন আশা করেন, বা সিস্টেমগুলোর মধ্যকার কোলিশন কম রাখতে চান তখন
UUIDব্যবহার করুন। - যদি আপনি ভবিষ্যতে টেন্যান্ট বা রিজিয়ন অনুযায়ী ডেটা ভাগ করতে পারেন, এমন একটি আইডি পরিকল্পনা চয়ন করুন যা নোডের মধ্যে কাজ করে (UUID, অথবা সমন্বিত bigint স্কিম)।
- একটি ডিফল্ট বেছে নিন এবং ব্যতিক্রম বিরল রাখুন। কনসিস্টেন্সি সাধারণত একাধিক টেবিল ক্ষুদ্রভাবে অপ্টিমাইজ করার চেয়ে বেশি কাজ করে।
লক ইন করার আগে একটি ছোট স্পাইক চালান। একটি বাস্তবসম্মত রো সাইজ সহ টেবিল তৈরি করুন, 1 থেকে 5 মিলিয়ন রো ইনসার্ট করুন, এবং তুলনা করুন (1) ইনডেক্স সাইজ, (2) ইনসার্ট টাইম, এবং (3) কিছু সাধারণ কোয়েরি প্রাইমারি কী ও কিছু সেকেন্ডারি ইনডেক্সসহ। আপনার বাস্তব হার্ডওয়্যার ও ডেটা আকারে এটি করুন।
আপনি যদি পরে বদলাতে ভয় পান, মাইগ্রেশনটা এমনভাবে পরিকল্পনা করুন যাতে সেটি বিরক্তিকর না হয়:
- নতুন ID কলাম ও একটি ইউনিক ইনডেক্স যোগ করুন।
- ডুয়াল-রাইট: নতুন রো-র জন্য দুইটি আইডি পূরণ করুন।
- ব্যাচে পুরনো রো-গুলো ব্যাকফিল করুন।
- API ও ক্লায়েন্ট আপডেট করুন যাতে নতুন আইডি গ্রহণ করে (ট্রানজিশনের সময় পুরনোটি কাজ করতেই থাকবে)।
- রিড কাট-ওভার করুন, তারপর লগ ও মেট্রিক ক্লিন হলে পুরনো কী ড্রপ করুন।
আপনি যদি AppMaster (appmaster.io) এ নির্মাণ করেন, আগে থেকেই সিদ্ধান্ত নেওয়া সুবিধাজনক কারণ আইডি কনভেনশন আপনার PostgreSQL মডেল, জেনারেটেড API, এবং ওয়েব ও নেটিভ মোবাইল অ্যাপে প্রবাহিত হয়। নির্দিষ্ট টাইপ গুরুত্বপূর্ণ, কিন্তু একবার আসল ব্যবহারকারী ও বহু ক্লায়েন্ট থাকলে ধারাবাহিকতা সাধারণত বেশি গুরুত্ব পায়।
প্রশ্নোত্তর
Default to bigint when you have a single PostgreSQL database, most writes happen on the server, and you care about compact indexes and predictable insert behavior. Pick UUIDs when IDs must be generated in many places (multiple services, offline mobile, future sharding) or when you don’t want public IDs to be easy to guess.
Because the ID gets copied into lots of places: the primary key index, every secondary index (as the row pointer), foreign key columns in other tables, and join tables. UUIDs are 16 bytes vs 8 bytes for bigint, so the size difference multiplies across your schema and can reduce cache hit rates.
On hot insert tables, yes. Random UUIDs (like v4) spread inserts across the whole B-tree, which increases page splits and index churn under load. If you want UUIDs but also want smoother writes, use a time-ordered UUID strategy so new keys land mostly at the end.
It often shows up as more IO, not slower CPU. Bigger keys mean bigger indexes, and bigger indexes mean fewer pages fit in memory, so joins and lookups can cause more reads. The difference is most noticeable on large tables, join-heavy queries, and systems where the working set doesn’t fit in RAM.
UUIDs help reduce easy guessing like /users/1, but they don’t replace authorization. If your permission checks are wrong, UUIDs can still be leaked and reused. Treat UUIDs as a convenience for public identifiers, and rely on strict access control for real security.
Use a single canonical representation and stick to it. A practical default is to treat IDs as strings in API requests and responses, even if the database uses bigint, because it avoids client-side numeric edge cases and keeps validation simple. Whatever you choose, make it consistent across web, mobile, logs, and caches.
Bigint can break in some clients if it’s parsed as a floating-point number, which can lose precision at large values. UUIDs avoid that because they’re strings, but they’re longer and easier to mishandle if you don’t validate strictly. The safest approach is consistency: one type everywhere, with clear validation at the API edge.
UUIDs are a straightforward choice because they can be created independently without coordinating a central sequence. Bigint can still work, but you need rules like per-shard ranges or a Snowflake-style generator, and you must enforce them forever. If you want the simplest distributed story, pick UUIDs (preferably time-ordered).
Changing the primary key type touches far more than one column. You must update foreign keys, join tables, API contracts, client storage, cached data, analytics events, mobile deep links, and any integrations that stored IDs as numbers or strings. If you might need a change, plan a gradual migration with dual-write and a long transition window.
Keep an internal bigint key for database efficiency, and add a separate public UUID (or token) for URLs and external APIs. That gives you compact indexes and human-friendly internal debugging while still avoiding easy enumeration in public-facing identifiers. The key is to decide early which one is the “public ID” and never mix them casually.


