NUMA را قورت بده! قسمت اول و دوم

N

NUMA چیست؟

در توضیح NUMA باید بدانید در دهه‌ اخیر سرعت محاسبات CPUها به شکل قابل توجهی افزایش یافته است، در این شرایط دسترسی سریع و با پهنای باند بالا به حافظه بیش از پیش مورد نیاز خواهد بود.

در معماری سنتی تعداد زیادی CPU باید برای دستیابی به حافظه بر سر پهنای باند BUS رقابت می­کردند؛ این معماری را اصطلاحاً Symmetric Multiprocessing (SMP) می‌گویند.

یک راه حل برای مشکل این است که بتوانیم از طریق چندین BUS به حافظه دسترسی داشته باشیم؛ این معماری جایگزین را اصطلاحاً NUMA (Non Uniform Memory Access ) می‌گویند.

در معماری -NUMA- چند پردازنده به همراه حافظه مستقل از طریق ارتباطات High Performance با هم در ارتباط هستند؛ اصطلاحاً هر کدام از این واحد‌های مستقل CPU و حافظه را NUMA Node می‌گویند.

NUMA

در معماری -NUMA- با وجود حل مشکل پهنای باند، چالش جدیدی به وجود آمده است.

واضح است که پردازش‌های روی یک Node، نیازمند اطلاعات داخل حافظه خواهند بود؛ این داده‌های مورد نیاز یا داخل حافظه محلی همان نود هستند، یا اینکه داخل حافظه سایر نودها.

به عبارتی در معماری -NUMA- دسترسی به حافظه یا Local است یا Remote.

مشکل اصلی این است که Remote Memory Access بر خلاف Local Memory Access با تاخیر همراه بوده و غیر بهینه است.

یکی از وظایف اصلی سیستم عامل این است که پردازش‌ها را به گونه‌ای Schedule کند که تا حد امکان، دسترسی به حافظه Local باشد.

مجازی سازی و NUMA

VMware از سال ۲۰۰۲ و از نسخه ESXi 1.5 با معماری NUMA کاملاً سازگار است.

برای همین منظور در ساختار VMkernel در کنار CPU Scheduler، ساختاری با نام NUMA Scheduler تعبیه شده است.

اگر ESXi روی سخت افزاری مبتنی بر معماری NUMA اجر شود، ماژول NUMA Scheduler فعال خواهد شد.

همچنین لازم است که قابلیت Node Interleaving (Interleaved Memory) در BIOS غیر فعال شده باشد تا ESXi سیستم را به عنوان NUMA تشخیص دهد.

علاوه بر این، سرور باید حداقل ۴ CPU Core و حداقل ۲ Core per NUMA Node داشته باشد.

هدف اصلی NUMA Scheduler بهینه سازی تخصیص CPU و RAM به ماشین مجازی است.

برای این منظور دو وظیفه اصلی بر عهده NUMA Scheduler گذاشته شده است:

۱٫ Initial Placement of VMs across NUMA Nodes

هر ماشین مجازی که توسط NUMA Scheduler مدیریت می‌شود، به یک نود اختصاص داده خواهد شد که اصطلاحاً به آن (NHN) Home Node NUMA گفته می‌شود.

زمانی که قرار است حافظه به ماشین مجازی تخصیص داده شود، ESXi تلاش خواهد کرد که حافظه را داخل Home Node اختصاص دهد و همچنین vCPUهای ماشین مجازی محدود به NUMA Home Node خواهند بود تا تضمین شود که دسترسی به حافظه Local و بهینه خواهد بود.

۲٫ Dynamically Load Balancing VMs across NUMA Nodes

با توجه تغییراتی که در Load سیستم در طول زمان ایجاد می‌شود، امکان دارد NUMA Scheduler برای حل مشکلات عدم توازن بین نودها، NUMA Home Node یک ماشین مجازی را تغییر دهد.

به صورت پیش فرض هر ۲ ثانیه یکبار سیستم بار موجود روی تمام نودها را بررسی خواهد کرد تا در صورت نیاز، Home Node یک یا چند ماشین مجازی را تغییر دهد.

اما از آنجا که اینکار ممکن است Remote Memory Access را افزایش دهد، NUMA Scheduler باید حافظه ماشین مجازی را به نود جدید منتقل کند (Page Migration).

برخی از ماشین‌های مجازی ممکن است توسط NUMA Scheduler مدیریت نشوند.

به عنوان مثال ماشین‌هایی که CPU Affinity یا Memory Affinity برای آن‌ها تعریف شده است.

بهینه سازی‌هایی که NUMA Scheduler اعمال خواهد کرد، کاملاً از دیدگاه Guest OS، Transparent خواهد بود؛

در نتیجه حتی روی سیستم عامل‌های قدیمی مثل Windows NT 4.0 که از معماری -NUMA- پشتیبانی نمی‌کنند نیز اعمال خواهد شد؛ و این خود یکی از مزایای مهم مجازی سازی است.

توجه دارید که تخصیص Physical CPU به ماشین مجازی و زمانبندی آن، همچنان وظیفه CPU Scheduler است نه NUMA Scheduler.

قبل از هر چیز لازم است که اجزای مختلف درگیر در این معماری را شرح دهیم. این اجزا در ۳ لایه زیر ایفای نقش خواهند کرد:

  • لایه فیزیکی
  • لایه VMkernel
  • لایه ماشین مجازی (VM)

NUMA

در رابطه با لایه فیزیکی منظور ما از CPU Package یک CPU است که روی Socket سوار می‌شود و در ساختار خودش Core، L1 Cache، L2 Cache و Last Level Cache (LLC) دارد.

این CPU Package در کنار Local Memory متصل، تشکیل یک NUMA Node را خواهد داد.

در لایه VMkernel مفهومی با نام pCPU پیاده سازی شده است که یک Abstraction از لایه فیزیکی است.

یک pCPU می‌تواند از یک Hyper-Threading (HT) استفاده کند که اصطلاحاً به آن Logical Processor گفته می‌شود، یا اینکه یک Core را در اختیار بگیرد.

در لایه ماشین مجازی نیز مفهومی با نام vCPU و Virtual Socket وجود دارد. یک vSocket می‌تواند به یک pCPU نگاشت (Map) شود یا اینکه ممکن است یک vSocket روی چند pCPU نگاشت شود (در حالتper Socket  Multi Core).

همانطور که pCPU یک مفهومی منطقی و انتزاعی ازPhysical CPU است، vCPU هم همین نقش را برای  pCPU بازی می‌کند.

به عبارت دقیقتر vCPU یک Logical Representation و Abstraction از pCPU می‌باشد.

اما در شکل فوق هنوز اشاره‌ای به ساختار -NUMA- نشده است. در ادامه به بررسی ساختار -NUMA- در معماری ESXi خواهیم پرداخت.

NUMA

گفتیم که وظیفه اصلی NUMA Scheduler یکی Initial Placement و دیگری Load Balancing بین NUMA Nodeهاست.

NUMA Scheduler برای این منظور دو ساختار منطقی با نام NUMA Home Node (NHN) و NUMA Client ایجاد کرده است.

 

NUMA Home Node NHN

NHN یک Logical Representation از CPU Package و Local Memory آن است.

NUMA Scheduler، Physical Coreهای موجود در CPU Package را شمارش می‌کند.

سپس نتیجه را با vCPUهای ماشین مجازی مقایسه می‌کند. این مقایسه برای Initial Placement و Load Balancing بسیار مهم است.

چنانچه تعداد vCPUهای یک ماشین مجازی بیشتر از تعداد Coreهای فیزیکی موجود در CPU Package باشد، لازم است که آن VM روی چند NUMA Node توزیع شود.

واضح است که با کاهش منطقی vCPUها می‌توان از این توزیع شدن روی چند NUMA Node جلوگیری کرد؛ هرچند که گاهی اوقات اجتناب ناپذیر خواهد بود!

به صورت پیش فرض فقط Coreها شمارش خواهد شد و Hyper-Threading را وارد بازی نخواهیم کرد!

توجه داشته باشید که فقط مساله شمارش vCPU و Physical Core نیست!

اگر RAM اختصاصی به یک ماشین مجازی بیشتر از RAM موجود در یک NUMA Node باشد، باز هم ناگزیر ماشین‌ مجازی روی چند NUMA Node توزیع خواهد شد هرچند که به صورت پیش فرض، NUMA Scheduler نسبت به این مساله ناآگاه است که در نهایت اگر مواظب نباشید باعث افت کارایی خواهد شد.

حال وظیفه NUMA Scheduler این است که تمام تلاش خود را انجام دهد تا به ماشین مجازی Local Memory تخصیص داده شود تا درگیر عملیات پر هزینه Remote Memory Access نشویم.

NUMA Client

یک NUMA Client، شامل vCPUها و حافظه‌های ماشین مجازی است که داخل یک NUMA Home Node جا (fit) می‌شوند.

NUMA Client کوچکترین واحدی است که NUMA Scheduler از آن برای Initial Placement و Load Balancing استفاده می‌کند.

وقتی یک ماشین مجازی را روشن می‌کنید، تعداد vCPUها شمارش و با تعداد Coreهای فیزیکی مقایسه می‌شود؛

اگر vCPUها کمتر بودند، یک توپولوژی Virtual UMA (Uniform Memory Access) به همراه یک Uniform Memory Address Space در اختیار ماشین مجازی قرار خواهد گرفت؛

در غیر این صورت باید چند NUMA Client برای آن ماشین مجازی ایجاد شود.

به عنوان مثال اگر یک ماشین مجازی با ۱۲ vCPU بخواهد روی یک سرور با CPU Package-10 Core روشن شود، باید دو NUMA Client ساخته شود و vCPUها به صورت مساوی بین این دو NUMA Client پخش شود.

NUMA

یک نکته بسیار مهم وجود دارد که توجه به آن حیاتی است!

بر خلاف آنچه در شکل فوق مشاهده می‌کنید، هیچ گونه وابستگی بین pCPUها (vCPUها) و NUMA Client وجود ندارد.

CPU Scheduler برای توازن و بهینه سازی، آزادانه می‌تواند تصمیم بگیرد که یک vCPU را روی هر کدام از pCPUهایی که CPU Package در اختیارش قرار داده، Schedule کند.

vNUMA Node

چنانچه برای یک ماشین مجازی بیش از یک NUMA client ایجاد شود، اصطلاحاً آن ماشین مجازی را Wide-VM می‌نامند.

در این شرایط NUMA Scheduler با هدف بهینه سازی بیشتر، برای این ماشین مجازی یک توپولوژی اختصاصی با نام Virtual NUMA (vNUMA) خواهد ساخت.

vNUMA مستقیماً در اختیار Guest OS قرار خواهد داد تا لازم نباشد ماشین مجازی و سیستم عامل درگیر توپولوژی -NUMA- سرور فیزیکی (PNUMA) شوند. این قابلیت از ESXi نسخه ۵ و VM Version نسخه ۸ به بعد معرفی شد.

به عنوان مثال در همان سناریو قبلی، وقتی یک ماشین مجازی با ۱۲ vCPU می‌سازیم، -vNUMA- دو نود مجازی که هر کدام ۶ vCPU دارد به سیستم عامل ارائه می‌کند؛

این موضوع باعث می‌شود اجازه دهیم خود Guest OS بتواند مستقلاً یک لایه NUMA Optimization را اعمال کند.

اگر قرار باشد به هر دلیلی بیش از یک vNUMA Client برای یک ماشین مجازی ایجاد شود، NUMA Scheduler مسئول Auto-Sizing برای vNUMA Clientها خواهد بود.

به صورت پیش فرض باید vCPUها به صورت مساوی بین کمترین تعداد NUMA Clientها تقسیم شوند.

عملیات Auto-Sizing در اولین باری که ماشین مجازی روشن شود، انجام خواهد گرفت.

در زمان اولین بار بوت شدن ماشین مجازی دو خط زیر به Advanced Settings ماشین مجازی افزوده خواهد شد:

numa.autosize.vcpu.maxPerVirtualNode=X

numa.autosize.cookie = ‘XXXXXX’

این تنظیمات تا زمانی که تعداد vCPUهای ماشین مجازی تغییر نکند، ثابت باقی خواهد ماند.

از طرفی عملیات Auto-Sizing با توجه به معماری Physical NUMA سروری انجام خواهد شد که ماشین مجازی برای اولین بار روی آن روشن شده است.

در نتیجه اگر کلاستری داشته باشیم که معماری -NUMA- سرورهای آن با هم متفاوت باشد، در نتیجه عملیات vMotion ممکن است به شدت باعث افت کارایی ماشین‌های مجازی شود.

در چنین شرایطی حتی Reboot کردن ماشین مجازی به امید Auto-Sizing مجدد با توجه به معماری جدید نیز بی‌فایده خواهد بود؛ مگر اینکه از دو تنظیم زیر در Advanced Settings ماشین مجازی استفاده کنیم:

numa.autosize.once = FALSE

numa.autosize = TRUE

اینکار باعث خواهد شد که بعد از هر Power-cycle و بعد از هر بار vMotion، NUMA Scheduler مجبور باشد که NUMA Client Size را تغییر دهد.

به شدت مواظب استفاده از این دو تنظیم باشید؛  چرا که لزوماً تمام Workloadها با تغییرات جدید NUMA Client Size به خوبی کنار نخواهند آمد!!

این خود انگیزه بسیار قدرتمندی خواهد بود تا شما را مجاب سازد کلاسترهایی با معماری یکسان (Homogeneous) ایجاد کنید.

تنظیمات پیشرفته معماری vNUMA

ممکن است شرایطی پیش آید که بخواهیم بنا به دلائلی، اندازه پیش فرض NUMA Client را تغییر دهیم؛

برای این منظور می‌توان از پارامترهای زیر جهت تغییر رفتار پیش فرض استفاده کنیم (هر چند این کار توصیه شده نیست، مگر اینکه آگاهی کامل از نتیجه کار داشته باشید!).

  1. numa.vcpu.min

به صورت پیش فرض -vNUMA- تنها برای ماشین‌هایی ایجاد خواهد شد که یکی از شرایط زیر را داشته باشند:

* ماشین‌هایی که تعداد vCPUهای آن‌ها ۹ یا بیشتر باشد.

* زمانی که تعداد vCPUهای ماشین مجازی بیشتر از تعداد Coreهای یک CPU Package باشد. گفتیم که این شمارش فقط تعداد Coreهای فیزیکی را در نظر خواهد گرفت نه Logical Processorها را!

با استفاده از این تنظیم می‌توان این حداقل، یعنی عدد ۹ را تغییر داد، تا برای ماشین‌هایی با vCPU کمتر نیز vNUMA ساخته شود.

  1. numa.vcpu.maxPerMachineNode

ممکن است برنامه‌ای داشته باشیم که حساس به Memory Bandwidth باشد نه Memory Access Latency! در چنین شرایطی حالت بهینه، بر خلاف حالت پیش فرض این خواهد بود که ماشین مجازی روی NUMA Nodeهای بیشتری توزیع شود تا پهنای باند بیشتری برای دسترسی به Memory داشته باشد.

این تنظیم به ما اجازه می‌دهد حداکثر تعداد vCPUهایی که داخل یک NUMA Client قرار خواهند گرفت را تغییر دهیم تا NUMA Clientهای بیشتری ساخته شود.

  1. Count Threads Not Cores

با استفاده از تنظیم numa.vcpu.preferHT=TRUE می‌توان NUMA Scheduler را مجبور کرد که در زمان شمارش، به جای شمارش Coreهای فیزیکی، تعداد Threadها یا Logical Processorها را مد نظر قرار دهد.

با استفاده از این تنظیم ممکن است بتوانیم شرایطی را ایجاد کنیم که یک Wide-VM بتواند روی یک NUMA Node، fit شود. به عنوان مثال یک ماشین مجازی با ۱۲vCPU روی یــــک سرور ۲ Socket – ۱۰ Core، دو NUMA Client ایجاد خواهد کرد (۶-۶).

اما با استفاده از numa.vcpu.preferHT=TRUE، NUMA Scheduler می‌تواند یک NUMA Client بسازد تا تمام vCPUها را روی یک CPU Package قرار دهد.

مجدداً توجه به این نکته ضروری است که در این حالت هم CPU Scheduler لزوماً مجبور نخواهد بود یک vCPU را روی یک Logical Processor (HT)، اجرا کند. CPU Scheduler همچنان تلاش خواهد کرد که یک vCPU بتواند یک Physical Core را تصاحب کند.

این موضوع بر عهده CPU Scheduler است نه NUMA Scheduler و وابسته به Ratio CPU Overcommitment در محیط است.

با وجود اینکه تکنولوژی Hyper-Threading در محیط‌ مجازی CPU Utilization را افزایش خواهد داد و Overall Performance را در حدود ۱۰-۱۵% بهبود خواهد بخشید؛ اما همانطور که می‌دانید، Logical Processorها منابع یک Physical Core را به اشتراک خواهد گذاشت؛

در نتیجه CPU Progression در مقایسه با حالتی که پردازش­ها‌ Physical Core را در اختیار دارند، کاهش خواهد یافت.

از اینرو لازم است بررسی کنیم که آیا ماشین مجازی و برنامه‌هایی که قرار است روی آن اجرا شوند، Cache Intensive هستند یا CPU-cycle Intensive؟ استفاده از numa.vcpu.preferHT=TRUE به CPU Scheduler دستور می‌دهد تا Memory Access را نسبت به CPU Resource در اولویت قرار دهد. همانطور که گفته شد، برای فعال کردن این گزینه، کاملاً آگاهانه و با تست کامل اقدام کنید؛ در غیر این صورت مقدار پیش فرض (False) را تغییر ندهید.

واضح است که در این شرایط هم لازم است میزان حافظه تخصیص داده شده به ماشین مجازی، کمتر یا مساوی با حافظه NUMA Node باشد، در غیر این صورت Remote Access اتفاق خواهد افتاد و کارایی preferHT را کاهش خواهد داد!

تنظیم این گزینه Per-VM خواهد بود، هر چند که می‌توان در صورت نیاز این گزینه را در سطح Host فعال کرد (KB2003582).

اگر این تنظیم را برای یک ماشین که قبلاً یکبار روشن شده است، اعمال کنیم، بنا به دلائلی که پیشتر گفته شد، تاثیری نخواهد داشت و نیاز به استفاده از تنظیمات تکمیلی دیگری خواهد بود.

  1. Cores per Socket

تنظیم Core per Socket (GUI) یا cpuid.coresPerSocket (Advanced Settings) به صورت پیش فرض روی عدد یک تنظیم شده است.

تغییر مقدار پیش فرض و افزایش آن باعث خواهد شد که -vNUMA- مستقیماً ساخته شده و در اختیار VM قرار گیرد که لزوماً ممکن است بهینه نباشد.

البته از نسخه ۶٫۵ به بعد، این تنظیم دیگر تاثیری در رفتار vNUMA نخواهد داشت و vNUMA تحت هر شرایطی حالت بهینه را در اختیار Guest OS قرار خواهد داد.

چگونه به معماری vNUMA دسترسی داشته باشیم؟

در داخل فایل VMware.log هر ماشین مجازی اطلاعاتی در رابطه با معماری و ساختار vNUMA وجود دارد. اما به جای دانلود و بررسی خط به خط این فایل می‌توان از دستور زیر استفاده کرد:

vmdumper -1 | cut -d \/ -f 2-5 | while read path; do egrep -oi DICT.* (displayname.*|numa.*|cores.*|vcpu.*|memsize.*|affinity.*)=.*|Inuma:.*|numaHost:.*’ “/$path/vmware.log”; echo -e; done

توجه داشته باشیم که برای کسب اطلاعات کامل، لازم است که حداقل یکبار ماشین مجازی روشن شده باشد تا ساختار NUMA Client، Auto-Size شده باشد.

درباره نویسنده

حسین تابش

حسین کارشناس ارشد مهندسی فناوری، اطلاعات گرایش شبکه‌های کامپیوتری هستش. نزدیک به ۸ سال میشه که از تکنولوژی مجازی استفاده میکنه و به شدت عاشق این تکنولوژی؛ خصوصا گرایش مجازی سازی سرور، شبکه و امنیت مجازی هستش.

افزودن دیدگاه

This site uses Akismet to reduce spam. Learn how your comment data is processed.

نوشته‌های تازه

آخرین دیدگاه‌ها