Golang в AeroFS

Переклад статті інженера компанії AeroFS про переведення їх микросервис-архітектури з Java на Go.

TLDR; Портировав деякі наші микросервисы з Java на Go, ми зменшили використання пам'яті на кілька порядків.

На початку була Java





Архітектура AeroFS Appliance складається з багатьох микросервисов, і переважна більшість з них написані на Java. Це ніколи не створювало нам проблем, вся система обслуговує тисячі користувачів від різних клієнтів без будь-яких проблем з продуктивністю.

Однак після нашого переходу на Docker, ми відзначили різке підвищення використання пам'яті нашою системою. Випробувавши кілька модних утиліт для моніторингу докерів, ми зупинилися на цьому кілька гиковском, але дуже корисний скрипт:
for line in `docker ps | awk '{print $1}' | grep-v CONTAINER`; do \
echo $(( `cat/sys/fs/cgroup/memory/docker/$line*/memory.usage_in_bytes` / 1024 / 1024 ))MB \
$(docker ps | grep $line | awk '{printf $NF" "}') ; \
done | sort-n

Він виводить список запущених контейнерів, відсортованих за кількістю використовуваної резидентної пам'яті. Приклад виведення скрипта:
web 46MB
Verification 66MB
Openid 74MB
82MB havre
105MB logcollection
146MB sp
181MB sparta

Дослідивши проблему, ми виявили, що деякі Java сервіси використали напрочуд багато пам'яті, часто ніяк не корелює з їх складністю або відсутністю такої. Ми виділили кілька головних факторів, які призводили до такого використання пам'яті.
  1. збільшення кількості запущених JVM, так як кожен tomcat servlet втік в окремому контейнері
  2. спрощена можливість для декількох JVM розділяти read-only-пам'ять: саму JVM, всі залежні бібліотеки, і, звичайно, безліч JAR-ів, що використовуються різними сервісами
  3. ізоляція пам'яті в деяких випадках збивала з пантелику евристику розрахунку пам'яті, що призводило до великих аллокациям кеша в деяких сервісах


Будучи людиною старої закалки, які звикли писати на асемблері для Z80 для пристроїв з 64кб пам'яті на борту, мене дуже надихала думка про те, як би повернути сотні мегабайт цінного оперативки. По щасливому випадку, наш наступний хакатон був всього через кілька днів, і це був відмінний шанс, щоб сфокусуватися на проблемі та відмінне виправдання, щоб спробувати нові інструменти.

Кодове ім'я: Greasefire

(прим. перекладача: greasefire — «пожежа, що виникла від спалаху масла/жиру на кухонній плиті»)
Моєю головною метою цього хакатона було пропалити шари метафоричного жиру, щоб зменшити загальну кількість використовуваної пам'яті AeroFS системи.

Зокрема, моїми критеріями успіху були:
  • використання CPU не повинно помітно зрости
  • стабільність і безпеку пам'яті повинно залишитися
  • використання резидентної пам'яті має зменшитися в 2 або більше разів
В дусі хакатона, я також хотів спробувати нові мови і інструменти, тому просто підправити існуючі сервіси було не варіант.

І щоб збільшити ймовірність отримання результату, який можна буде показати і, можливо, навіть задеплоить, було важливо вибрати адекватного розміру і складності мета. Очевидним вибором став сервіс TeamServer probe (team-servers на сторінці appliance status) — маленький tomcat servlet з єдиним HTTP-викликом і дуже ясною внутрішньою логікою.

У підсумку, мета була наступна — створити сервер:
  • з повністю ідентичним API
  • упакований в docker-імідж


Пробуємо нові інструменти

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

Пул кандидатів швидко звузився до двох учасників — Go і Rust. Обидва досить легко компілюють код в невеликі статичні бінарні файли, ідеально заточені для запуску в мінімальних контейнерах. Обидва обіцяли адекватну продуктивність, збереження пам'яті, хорошу підтримку конкурентного програмування і, що було особливо важливо для мене, менше використання пам'яті, ніж у випадку з JVM.

Хитромудра система типів Rust виглядала особливо привабливо. Але при цьому, Rust був набагато менш зрілим, ніж Go, на той момент ще навіть не досягли версії 1.0. Вибору Rust також заважала відсутність хороших бібліотек для HTTP та низькорівневої роботи з мережею.

Раніше ми вже пробували перенести один з наших сервісів на Go, році так в 2013-му, але на той момент ми потрапили на якусь витік пам'яті, і вирішили припинити експеримент. Через два роки, Go виглядав набагато більш зрілим і був виділений як найкращий кандидат для нашого експерименту.

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

Я також був дуже втішений наявністю єдиного стандарту форматування мови, і магічною утиліти gofmt, яка примушує до нього і дуже легко інтегрується з текстовим редактором начебто vim (невеликий інсайт: цей хакатон також був моєю першою спробою використовувати vim для чогось більшого, ніж однорядкове редагування)



Результати

У мене зайняло близько дня, щоб познайомитися з Go і портувати простий сервіс, вибраний для хакатона. Результати були дуже багатообіцяючими:
  • Розмір коду був зменшений удвічі, з 175 рядків до 96
  • Використання резидентної пам'яті впало з 87MB до всього-лише 3MB, 29х зменшення!
  • Результуючий docker-імідж зменшився з 668MB до 4.3 MB — це 155х зменшення! Згоден, що найбільші шари докер-іміджів все одно переиспользовались різними сервісами, тому реальне зменшення використання диска було набагато менше при використанні багатьох Java-сервісів. Тим не менше, ці цифри дуже радували око.


До хакатона залишався ще майже цілий день, і я звернув увагу на ще один сервіс — Certificate Authority (ca на appliance status page). Цей сервіс приймає запити на підпис сертифікатів від внутрішніх сервісів і десктоп-клієнтів і повертає підписані сертифікати, що використовуються для шифрування пересилання як peer-to-peer контенту між клієнтами, так і для клієнт-серверної комунікації.

Коли цей новий CA нарешті замінив свій Java-еквівалент, через кілька днів після закінчення хакатона, він зменшив використання пам'яті на неймовірні 100х!

Цей проект виграв номінацію «Технічна крутизна» (ориг. «Technical Amazingness»), і перетворився на триваючі зусилля по зменшенню використання пам'яті всієї системи.

До версії 1.1.5 ще чотири сервісу були портіровани на Go — 6 в цілому — і сумарна економія пам'яті склала 1 Гб. В кожному випадку ми отримували аналогічне зменшення в розмірі коду, і в деяких випадках ми навіть отримали значне зменшення використання процесора або кращу пропускну здатність.

— Hugues & the AeroFS Team.

Джерело: Хабрахабр

0 коментарів

Тільки зареєстровані та авторизовані користувачі можуть залишати коментарі.