Коли ви справді змушені використовувати UUID як частину дизайну?


123

Я не бачу сенсу UUID . Я знаю, що ймовірність зіткнення фактично дорівнює нулю , але фактично нуль навіть не близька до неможливої.

Чи може хтось навести приклад, коли у вас немає іншого вибору, крім використання UUID? З усіх застосувань, які я бачив, я бачу альтернативний дизайн без UUID. Звичайно, конструкція може бути дещо складнішою, але, принаймні, не має нульової ймовірності виходу з ладу.

UUID пахне мені глобальними змінними. Є багато способів глобальних змінних зробити простішим дизайном, але його просто ледачим дизайном.


23
Все має ненульовий шанс невдачі. Я б зосередився на набагато більше шансах на те, що виникнуть проблеми (тобто майже на все, що ви можете придумати), ніж на зіткнення UUID
DanSingerman

16
Насправді "фактично нульовий" дуже близький до неможливого.
mqp

21
Ні, його насправді нескінченно далеко неможливо
Піролістичний

32
@Pyrolistic, коли ви починаєте обробляти слова типу "нескінченність", ви покинули світ розробки програмного забезпечення. Теорія інформатики - це зовсім інша дискусія, ніж написання реального програмного забезпечення.
Рекс М

Відповіді:


617

Я написав генератор / аналізатор UUID для Ruby, тому вважаю себе досить добре поінформованим з цього приводу. Існує чотири основні версії UUID:

UUID версії 4 - це лише 16 байт випадковості, витягнутий з криптографічно захищеного генератора випадкових чисел, з деяким подвійним змітанням для ідентифікації версії та варіанта UUID. Вони вкрай навряд чи зіткнуться, але це може статися, якщо використовується ПРНГ або якщо у вас просто трапиться справді, справді, справді, справді, дуже невдача.

Версії 5 та версії 3 UUID використовують хеш-функції SHA1 та MD5 відповідно, щоб об'єднати простір імен з частиною вже унікальних даних для генерації UUID. Наприклад, це дозволить створити UUID з URL-адреси. Зіткнення тут можливі лише в тому випадку, якщо основна хеш-функція також має зіткнення.

UUID версії 1 є найбільш поширеними. Вони використовують MAC-адресу мережевої картки (яка, за винятком випадків, коли вона підроблена, повинна бути унікальною), плюс часову позначку, плюс звичайне подвійне подвійне створення для UUID. У випадку з машиною, яка не має MAC-адреси, 6 байтів вузлів генеруються за допомогою криптографічно захищеного генератора випадкових чисел. Якщо два UUID генеруються в послідовності досить швидко, що часова марка відповідає попередній UUID, часова марка збільшується на 1. Зіткнення не повинно відбуватися, якщо не трапиться одне з наступних дій: MAC-адреса підроблена; Одна машина з двома різними програмами, що генерує UUID, виробляє UUID в той самий момент; Дві машини без мережевої карти або без доступу користувача на MAC-адресу отримують однакову послідовність випадкових вузлів і генерують UUID в той самий момент;

Реально, жодна з цих подій не відбувається випадково в просторі ідентифікатора однієї програми. Якщо ви не приймаєте посвідчення особи, скажімо, в масштабах Інтернету чи в ненадійному оточенні, де зловмисники можуть зробити щось погане у випадку зіткнення ідентифікатора, це просто не те, про що ви повинні турбуватися. Важливо розуміти, що якщо вам трапляється генерувати той самий UUID версії 4, що і я, в більшості випадків це не має значення. Я створив ідентифікатор у абсолютно іншому просторі ідентифікаторів від вашого. Моя програма ніколи не дізнається про зіткнення, тому зіткнення не має значення. Відверто кажучи, в єдиному просторі застосування без шкідливих акторів вимирання всього життя на Землі відбудеться задовго до того, як у вас відбудеться зіткнення, навіть у версії 4 UUID, навіть якщо ви '

Також 2 ^ 64 * 16 - 256 екбабайтів. Як і в, вам потрібно буде зберігати ідентифікатори на суму 256 екзабайтів, перш ніж у вас виникне 50% шанс зіткнення ідентифікатора в одному просторі програми.


8
Це, безумовно, найкраще пояснення. Я не знаю, чому за це не проголосують вгорі. Кудо тобі споркмонгер.
Бред Баркер

1
@Chamnap Я написав UUIDTools. UUID можуть бути перетворені на ціле число або їх необроблену байтову форму і були б значно меншими як двійкові.
Боб Аман

1
@Chamnap uuid.rawнадасть вам рядок байтів. hashМетод не є корисним для вас. Він використовується для хеш-таблиць та операцій порівняння внутрішньо в Ruby. Усі методи перетворення в різні представлення UUID і з них визначаються як методи класу і мають бути префіксом "parse".
Боб Аман

3
@BobAman в 1990 році у мене відбулося 12 зіткнень UUID в системі Aegis, виявилося несправним FPU, але я подумав, що я можу вам повідомити, що це може статися (не трапилось інакше, ніж за останні 30+ років програмування) . Приємне пояснення, занадто btw, це тепер мій дефакто затриманий пост UUID, щоб дати людям :)
GMasucci

2
@kqr Ви абсолютно праві, що це проблема з днем ​​народження, проте для n-бітового коду проблема парадоксального дня народження зменшується до 2 ^ (n / 2), що в цьому випадку становить 2 ^ 64, як зазначено у моїй відповіді .
Боб Аман

69

Те, що UUID купують у вас, що дуже важко зробити інакше, - це отримати унікальний ідентифікатор без необхідності консультуватися або погоджуватися з центральним органом . Загальна проблема можливості отримати таке, не маючи керованої інфраструктури, - це проблема, яку вирішують UUID.

Я читав, що відповідно до парадокса дня народження шанс виникнення зіткнення UUID становить 50%, як тільки буде сформовано 2 ^ 64 UUID. Зараз 2 ^ 64 - це досить велика кількість, але 50% шанс зіткнення здається занадто ризикованим (наприклад, скільки UUID потрібно існувати, перш ніж є 5% шанс зіткнення - навіть це здається занадто великою ймовірністю) .

Проблема з цим аналізом є двоякою:

  1. UUID не зовсім випадкові - є основні компоненти UUID, які залежать від часу та / або місцеположення. Таким чином, щоб мати будь-який реальний шанс при зіткненні, що стикаються UUID повинні бути генеровані в той самий час з різних генераторів UUID. Я б сказав, що хоча є обґрунтований шанс, що одночасно може генеруватися кілька UUID, є достатня кількість інших (включаючи інформацію про місцезнаходження або випадкові біти), щоб зробити ймовірність зіткнення цього дуже невеликого набору UUID майже неможливою. .

  2. строго кажучи, UUID повинні бути унікальними лише для набору інших UUID, з якими вони можуть порівнюватися. Якщо ви генеруєте UUID для використання в якості ключа бази даних, не має значення, якщо десь ще в злому альтернативному всесвіті використовується той самий UUID для ідентифікації інтерфейсу COM. Так само, як це не призведе до плутанини, якщо на Альфа-Кентаврі є ще хтось (чи щось таке) на ім'я "Майкл Берр".


1
Конкретний приклад? COM / DCE UUID - немає повноважень для їх присвоєння, і ніхто не хотів брати на себе відповідальність і / або ніхто не хотів, щоб там було повноваження. Поширені бази даних, які не мають надійних посилань і не мають головного.
Майкл Берр

3
Більш конкретний приклад - банківська програма. У ньому встановлено кілька центрів обробки даних, по одному для кожної країни, при цьому кожен центр даних має БД. Кілька установок існують для дотримання різних норм. Усього набору для кожного клієнта може бути лише один запис клієнта .....
Vineet Reynolds

(Продовження попереднього коментаря) Потрібно мати центральний сервер для створення ідентифікатора клієнта для загальних цілей звітності та відстеження (для всіх установок) або окремі установки генерувати UUID, щоб слугувати ідентифікаторами клієнтів (очевидно, UUID не можна використовувати як у у звітах).
Vineet Reynolds

На той час, коли у вас є 50% шанс на дублювання, ви вже потопите. Хтось зазначає обсяг, необхідний для отримання шансу 0,0000001%. Кілька баз даних з автоматичним збільшенням, починаючи від 1 до n і щоразу збільшуючись на n, ефективно вирішує ту саму проблему.
Гордон

2
Шанси на отримання дубліката є FAR, FAR нижчі, ніж шанси центрального органу, який
зазнає

33

Все має ненульовий шанс невдачі. Я б зосередився на набагато більшій кількості шансів на те, що виникнуть проблеми (тобто майже на все, що ви можете придумати), ніж на зіткнення UUID


Додано як відповідь на запит компанії
Pyrolistic

16

Акцент на "розумно" або, як ви сказали, "ефективно": досить добре - це те, як працює реальний світ. Обсяг обчислювальної роботи, що займається покриттям цього розриву між "практично унікальним" та "справді унікальним", величезний. Унікальність - крива зі зменшенням віддачі. У якийсь момент на цій кривій є лінія між тим, де "достатньо унікальний" все ще доступний, і тоді ми кривимо ДУЖЕ круто. Вартість додавання більшої унікальності стає досить великою. Нескінченна унікальність має нескінченну вартість.

UUID / GUID - це, порівняно кажучи, обчислювально швидкий та простий спосіб генерувати ідентифікатор, який можна обгрунтовано вважати універсальним унікальним. Це дуже важливо для багатьох систем, яким потрібно інтегрувати дані з раніше не пов'язаних систем. Наприклад: якщо у вас є система управління вмістом, яка працює на двох різних платформах, але в якийсь момент потрібно імпортувати вміст з однієї системи в іншу. Ви не хочете змінювати ідентифікатори, тому ваші посилання на дані системи A залишаються цілими, але ви не хочете, щоб зіткнення з даними, створеними в системі B. UUID вирішує це.


Рішення. Не лінуйтеся і оновлюйте посилання. Зробіть це правильно.
Піролістичний

8
Це не має нічого спільного з ледачістю - якщо політика полягає в тому, що ідентифікатор для елемента вважається постійним і незмінним, то ідентифікатор не змінюється. Таким чином, ви хочете, щоб ідентифікатори були унікальними з самого початку, і ви хочете зробити це з тим, щоб з початку вимагати, щоб усі системи були певним чином підключені.
Майкл Берр

Тоді вам потрібен контекст. Якщо у вас є дві групи унікальних ідентифікаторів, які можуть конфліктувати, вам потрібен високий рівень контексту, щоб розділити їх
Pyrolistic

23
Або ви можете просто побудувати систему для використання UUID та відправити її, продати, заробити мільйон доларів і ніколи не почути жодної скарги на те, що два ідентифікатори зіткнулися, бо цього не станеться.
Рекс М

16

Ніколи абсолютно не потрібно створювати UUID. Однак, як би зручно було існувати стандарт, коли користувачі в режимі офлайн можуть створювати ключ до чогось із дуже низькою ймовірністю зіткнення.

Це може допомогти у вирішенні реплікації бази даних тощо ...

Користувачам в Інтернеті було б легко створити унікальні ключі для чогось без накладних витрат або можливості зіткнення, але це не те, для чого призначені UUID.

У будь-якому разі, слово про вірогідність зіткнення, взяте з Вікіпедії:

З огляду на ці цифри, щорічний ризик ураження метеоритом оцінюється як один шанс у 17 мільярдів, що еквівалентно шансу створити кілька десятків трильйонів UUID в рік та мати один дублікат. Іншими словами, лише після генерування 1 мільярда UUID щосекунди протягом наступних 100 років, ймовірність створення лише одного дубліката складе близько 50%.


4
Прості, не дозволяйте користувачам офлайн генерувати ключі. Поставте тимчасові ключі, поки система не вийде в Інтернет, щоб справжні ключі могли генеруватися.
Піролістичний

На мій погляд, це дуже корисна відповідь ... збирався запропонувати якусь аналогію ймовірності сам, оскільки, здавалося, ОП не зовсім зрозумів, що це має сенс, але ви, здається, це зробили.
Нолдорін

Я тихо розумію, що ймовірність фактично нульова. Для мене використання UUID - це лінивий дизайн, і я просто хотів побачити, чи можна завжди цього уникати
Pyrolistic

Це досить справедливо, доки ви бачите, що низьку ймовірність потрібно навіть враховувати в самих екстремальних обставинах, як я зараз припускаю, що ви це робите.
Нолдорін

13

Класичний приклад - коли ви реплікуєте між двома базами даних.

БД (A) вставляє запис з int ID 10 і в той же час DB (B) створює aa запис із ID 10. Це зіткнення.

З UUID це не відбудеться, оскільки вони не збігаються. (майже напевно)


1
Гаразд, тоді змушуйте DB A використовувати парні ідентифікатори, а DB B використовувати непарні ідентифікатори. Зроблено, немає UUID.
Піролістичний

2
З трьома БД використовуйте 3 кратні LOL
Jhonny D. Cano -Leftware-

20
Якщо ви використовуєте 2/3 / що б то не було кратні, що буде, коли ви додасте новий сервер у суміш пізніше? Вам потрібно узгодити комутатор, щоб ви використовували n + 1 кратні на новому сервері, і переміщували всі старі сервери до нового алгоритму, і вам доведеться все вимкнути, поки ви робите це, щоб уникнути зіткнень під час перемикач алгоритму. Або ... ви можете просто використовувати UUID, як ВСІЙ ЕЛЕЙС.
Боб Аман

3
Це навіть гірше за це, бо як би ви розмежовували кратні 2 та кратні 4? Або кратні 3 або кратні 6? Насправді вам доведеться дотримуватися кратних простих чисел. Blech! Просто використовуйте UUID, він працює. Microsoft, Apple і незліченна кількість інших покладаються на них і довіряють їм.
sidewinderguy

2
@sidewinderguy, в GUID ми довіряємо! :)
Рон Кляйн

13

Існує також ненульова ймовірність того, що кожна частинка у вашому тілі одночасно пройде тунель через стілець, на якому ви сидите, і ви раптом опинитеся сидячи на підлозі.

Ви турбуєтесь про це?


7
Звичайно ні, це не те, що я можу контролювати, але проекти, які я можу.
Піролістичний

4
@Pyrolistical Is , що на насправді, я маю в виду на насправді причина вам не турбуватися про це? Тоді ти досить дивно. І більше того, ти не правий. Ви можете керувати ним. Якщо ви наберете кілька кілограмів, ви значно зменшите ймовірність такої події. Чи вважаєте ви, що ви повинні набрати вагу? :-)
Veky

8

У мене є схема уникнення UUID. Налаштуйте сервер десь і надіньте його таким чином, щоб кожен раз, коли якийсь фрагмент програмного забезпечення хотів універсальний унікальний ідентифікатор, вони звертаються до цього сервера і він передає його. Просто!

За винятком того, що з цим є деякі реальні практичні проблеми, навіть якщо ми ігноруємо відверту злобу. Зокрема, цей сервер може вийти з ладу або стати недосяжним через частину Інтернету. Справа з відмовою на сервері вимагає реплікації, і це дуже складно, щоб правильно (див. Літературу про алгоритм Paxos, чому створення консенсусу незручно) і досить повільно. Крім того, якщо всі сервери недоступні для певної частини мережі, жоден з клієнтів, підключених до цієї підмережі, не зможе зробити нічого, тому що всі вони будуть чекати нових ідентифікаторів.

Отже ... використовуйте простий ймовірнісний алгоритм для їх генерування, що навряд чи вийде з ладу протягом життя Землі, або (фінансуйте та) побудуйте основну інфраструктуру, яка буде розгортанням PITA і має часті збої. Я знаю, на який би я пішов.


2
Власне, вся суть винаходу UUID полягала у тому, щоб уникнути вашого підходу. Якщо ви досліджуєте історію UUID, ви побачите, що це випливає з найдавніших експериментів зі створення складних та значущих мереж комп'ютерів. Вони знали, що мережі за своєю суттю ненадійні та складні. UUID відповідали на питання, як координувати дані між комп’ютерами, коли ви знали, що вони не можуть бути в постійному спілкуванні.
Василь Бурк

7
@BasilBourque Я використовував сарказм у тому першому пункті, якщо це не було очевидно.
Дональні стипендіати

5

я не веду всіх розмов про ймовірність зіткнення. Мене не хвилює зіткнення. Я хоч і дбаю про продуктивність.

https://dba.stackexchange.com/a/119129/33649

UUID - це катастрофа ефективності роботи дуже великих таблиць. (200К рядків не "дуже великі".)

Ваш номер 3 справді поганий, коли НАЗАД ЗАРЯДУ є utf8 - CHAR (36) займає 108 байт!

UUID (GUID) дуже "випадкові". Використання їх як УНІКАЛЬНОГО або ПРИМІТНОГО клавіші на великих столах дуже неефективно. Це пов’язано з тим, що вам доведеться стрибати навколо таблиці / індексу щоразу, коли ВСТАВЛЯєте новий UUID або ВИБІРУВАТИ UUID. Коли таблиця / індекс занадто велика, щоб вмістити кеш (див. Innodb_buffer_pool_size, який повинен бути меншим, ніж оперативна пам’ять, як правило, 70%), "наступний" UUID може не кешуватися, отже, повільний диск. Якщо таблиця / індекс у 20 разів більша, ніж кеш, кешується лише 1/20 (5%) звернень - ви пов'язані введенням / виводом.

Таким чином, не використовуйте UUID, якщо це не так

у вас є "маленькі" таблиці, або вони вам справді потрібні через генерування унікальних ідентифікаторів з різних місць (і ви не знайшли іншого способу зробити це). Детальніше про UUID: http://mysql.rjweb.org/doc.php/uuid (Він включає функції для перетворення між стандартними 36-знаковими UUID та BINARY (16).)

Маючи в одній таблиці і УНІКАЛЬНУ AUTO_INCREMENT, і УНІКАЛЬНУ UUID, це марно.

Якщо виникає INSERT, всі унікальні / первинні ключі повинні бути перевірені на наявність дублікатів. Будь-якого унікального ключа достатньо для вимоги InnoDB мати ПЕРШИЙ КЛЮЧ. BINARY (16) (16 байт) дещо громіздкий (аргумент проти того, щоб зробити його ПК), але не так вже й погано. Об'ємність має значення, коли у вас є вторинні ключі. InnoDB мовчки торкається ПК до кінця кожної вторинної клавіші. Основний урок тут - мінімізувати кількість вторинних ключів, особливо для дуже великих таблиць. Для порівняння: INT UNSIGNED - це 4 байти з діапазоном 0..4 мільярда. BIGINT - 8 байт.


4

Якщо ви просто подивитесь на альтернативи, наприклад, для простого додатка для баз даних, що потрібно запитувати базу даних кожного разу перед створенням нового об'єкта, ви скоро виявите, що використання UUID може ефективно звести до складності вашої системи. Звичайно - якщо ви використовуєте клавіші int, вони є 32-бітовими, які зберігатимуться в чверті 128-бітного UUID. Зрозуміло - алгоритми генерації UUID займають більше обчислювальної потужності, ніж просто збільшення числа. Але - кого турбує? Накладні витрати на керування "повноваженнями" для присвоєння інакше унікальних чисел легко перевершують, що на порядки величини, залежно від призначеного простору ідентифікаційного коду.


3

На UUID == лінивий дизайн

Я не погоджуюсь з вибором ваших боїв. Якщо дублікат UUID статистично неможливий і математика доведена, то навіщо турбуватися? Витрачати час на розробку вашої невеликої системи, що генерує N UUID, недоцільно, завжди є десяток інших способів вдосконалити систему.


1

На моїй останній роботі ми отримували об'єкти від сторонніх осіб, які були однозначно ідентифіковані з UUID. Я помістив UUID-> довгу цілу таблицю пошуку і використовував довге ціле число в якості моїх первинних ключів, оскільки це було набагато швидше таким чином.


Безперечно, третя сторона, що змушує вас використовувати UUID, - це ще одна проблема, до якої я не хочу вступати. Якщо припустити, ви маєте контроль над використанням UUID чи ні.
Піролістичний

Ну, "довге ціле число" (128 біт) - це насправді те, що є UUID. Це просто показано як рядок для споживання людиною. Іноді це може бути передано таким чином, але для зберігання та індексації це, безумовно, буде швидше у цілій формі, як ви знайшли.
Ніколь

1

Використовуючи алгоритм версії 1, здається, що неможливо зіткнення під обмеженням, що менше 10 UUID на мілісекунд генеруються з тієї ж MAC-адреси

Концептуально, оригінальна схема (версія 1) генерації UUID повинна була поєднати версію UUID з MAC-адресою комп'ютера, що генерує UUID, та з кількістю інтервалів 100 наносекунд з моменту прийняття григоріанського календаря на Заході . На практиці власне алгоритм складніший. Цю схему піддавали критиці тим, що вона не є достатньо «непрозорою»; він розкриває як особистість комп'ютера, що генерував UUID, так і час, коли він це зробив.

Хтось мене виправить, якщо я неправильно трактував, як це працює


Існує багато версій, і багато програмних систем (наприклад, Java) не можуть використовувати версію 1, оскільки у неї немає чистого способу доступу Java до mac-адреси.
Піролістичний

Щодо неможливості Java отримати MAC-адресу: Не зовсім вірно. Для цього існують робочі місця. Ви можете вручну встановити MAC-адресу, яку використовує генератор, через конфігураційний файл. Ви також можете зателефонувати на ifconfig та проаналізувати вихід. Генератор Ruby UUID, який я написав, використовує обидва підходи.
Боб Аман

Крім того, як було сказано у моїй відповіді, якщо ви не можете отримати MAC-адресу для UUID версії 1, ви використовуєте замість цього 6 випадкових байтів, відповідно до розділу 4.5 RFC 4122. Тож навіть якщо ви не хочете використовувати жоден з два обхідних шляхи для Java, ви все ще можете створити дійсну UUID версії 1.
Боб Аман

MS GUID - це лише випадкові числа. У них більше немає MAC-частини, тому що це дозволило переробити інженер MAC-адресу сервера (що виявилося дуже небезпечним).
Стефан Штайгер

1

Тим, хто говорить, що UUID - це поганий дизайн, тому що вони можуть (за певною смішною невеликою ймовірністю) зіткнутися, тоді як ваші ключі, згенеровані БД, не будуть ... ви знаєте ймовірність помилки людини, яка призведе до зіткнення ваших ключів, створених БД, через деякі -помічена потреба на FAR FAR FAR вище, ніж шанс зіткнення UUID4. Ми знаємо, що якщо відтворити db, він знову запустить ідентифікатори на 1, і скільки з нас довелося відтворити таблицю, коли ми були впевнені, що ніколи не буде потрібно? Я б поклав свої гроші на безпеку UUID, коли в будь-який день починаються речі невідомі з невідомими.


0

Крім випадків, коли вам доведеться використовувати чужий API, який вимагає UUID, звичайно, завжди є інше рішення. Але чи вирішать ці альтернативи всі проблеми, які мають UUID? Чи зможете ви додати більше шарів хаків, кожен з яких вирішить іншу проблему, коли ви могли б вирішити їх усі відразу?

Так, теоретично можливо, що UUID стикаються. Як зазначають інші, це смішно малоймовірно до того, що це просто не варто думати. На сьогодні це ніколи не бувало і, швидше за все, ніколи не буде. Забути про це.

Найбільш "очевидний" спосіб уникнути зіткнень - це дозволити одному серверу генерувати унікальні ідентифікатори на кожній вставці, що, очевидно, створює серйозні проблеми з продуктивністю і зовсім не вирішує проблему генерації в автономному режимі. На жаль

Інше "очевидне" рішення - це центральний орган, який заздалегідь роздає блоки унікальних номерів, що по суті є тим, що робить UUID V1, використовуючи MAC-адресу генеруючої машини (через IEEE OUI). Але повторювані MAC адреси трапляються, тому що кожен центральний орган зрештою викручується, тому на практиці це набагато частіше, ніж зіткнення UUID V4. На жаль

Найкращий аргумент проти використання UUID - це те, що вони "занадто великі", але (значно) менша схема неминуче не зможе вирішити найцікавіші проблеми; Розмір UUID - це властива побічна дія їх корисності при вирішенні цих самих проблем.

Можливо, ваша проблема недостатньо велика, щоб потребувати того, що пропонують UUID, і в цьому випадку сміливо використовуйте щось інше. Але якщо ваша проблема зростає несподівано (і це роблять більшість), ви закінчите перемикання пізніше - і беріть себе за те, що не використовуєте їх в першу чергу. Навіщо проектувати на провал, коли настільки ж просто спроектувати успіх?


-10

UUID втілюють усі погані практики кодування, пов'язані з глобальними змінними, ще гірше, оскільки вони є надглобальними змінними, які можуть бути розподілені по різних частинах набору.

Нещодавно потрапив у таке питання із заміною принтера на точну модель заміни, і виявив, що жодне з клієнтських програм не працює.


2
Рада, що ми живемо в суспільстві, яке все ще фокусується на фактах, на відміну від випадкових думок, інакше всі ми, що переповнюють стік, були б без роботи. :)
Макаранд
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.