<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:wfw="http://wellformedweb.org/CommentAPI/" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:sy="http://purl.org/rss/1.0/modules/syndication/" xmlns:slash="http://purl.org/rss/1.0/modules/slash/">
  <channel>
    <language>ru</language>
    <title>Масштабируемость и ограничения памяти</title>
    <description/>
    <link>https://tproger.ru/tag/scalability-and-memory-limits</link>
    <atom:link href="https://tproger.ru/tag/scalability-and-memory-limits/feed" rel="self" type="application/rss+xml"/>
    <lastBuildDate>Wed, 21 Oct 2026 05:04:46 GMT</lastBuildDate>
    <sy:updatePeriod>hourly</sy:updatePeriod>
    <sy:updateFrequency>12</sy:updateFrequency>
    <image>
      <title>Масштабируемость и ограничения памяти</title>
      <link>https://tproger.ru</link>
      <url>https://tproger.ru/apple-touch-icon.png</url>
    </image>
    <item>
      <title>Семь ошибок индексации БД, которые убивают производительность SaaS на корню</title>
      <link>https://tproger.ru/articles/sem-owibok-indeksacii-bd-kotorye-ubivayut-proizvoditelnost-sa</link>
      <comments>https://tproger.ru/articles/sem-owibok-indeksacii-bd-kotorye-ubivayut-proizvoditelnost-sa?scrollTo=tproger-comments</comments>
      <dc:creator><![CDATA[Евгений Стребков]]></dc:creator>
      <guid isPermaLink="true">https://tproger.ru/articles/sem-owibok-indeksacii-bd-kotorye-ubivayut-proizvoditelnost-sa</guid>
      <description><![CDATA[<p>Разбираем 7 типичных ошибок индексации в PostgreSQL и MySQL: переиндексация, низкая селективность, раздутие индексов, мультитенантность и внешние ключи. Проверьте свою БД перед релизом.</p><p>— Читать дальше «<a rel="follow" href="https://tproger.ru/articles/sem-owibok-indeksacii-bd-kotorye-ubivayut-proizvoditelnost-sa">Семь ошибок индексации БД, которые убивают производительность SaaS на корню</a>»</p>]]></description>
      <category><![CDATA[Алгоритмы и структуры данных]]></category>
      <category><![CDATA[Масштабируемость и ограничения памяти]]></category>
      <category><![CDATA[Оптимизация]]></category>
      <category><![CDATA[Производительность]]></category>
      <category><![CDATA[MySQL]]></category>
      <category><![CDATA[SQL]]></category>
      <category><![CDATA[PostgreSQL]]></category>
      <category><![CDATA[Статьи]]></category>
      <slash:comments>0</slash:comments>
      <pubDate>Wed, 03 Jun 2026 05:30:09 GMT</pubDate>
      <content:encoded><![CDATA[<p>Ваш код чист, архитектура продумана, а запросы на staging укладываются в 12 мс. Но при 500 000 записей всё встаёт: панели мониторинга тормозят, пользователи жалуются, а дежурный инженер в полночь разглядывает план выполнения запроса и не понимает, что пошло не так. В девяти случаях из десяти причина — не отсутствие индексов, а <b>неправильные индексы</b>.</p><p><b>Индекс в базе данных</b> — это вспомогательная структура (чаще всего B-дерево; хеш-индексы, GiST, GIN и другие типы применяются в узких специфических сценариях), которая ускоряет выборку строк по заданным колонкам. По аналогии с оглавлением книги: вместо перелистывания всех страниц СУБД сразу переходит к нужной главе. Но если оглавление построено плохо, пользы от него нет — а вот накладные расходы на поддержку остаются.</p><p>В этой статье разберём семь самых разрушительных ошибок индексации в production-SaaS: от параноидального создания индексов «на всякий случай» до игнорирования раздутия и особенностей мультитенантных схем. Каждая ошибка — с примерами SQL, метриками и конкретным фиксом.</p><p>Индексы — это налог на запись: каждый INSERT, UPDATE и DELETE обновляет все индексы таблицы.</p><p>Индекс на колонке с низкой селективностью (boolean, статус) почти бесполезен — используйте частичные (partial) индексы.</p><p>В составном индексе порядок колонок критичен: PostgreSQL может использовать (a, b, c) только если условие начинается с a.</p><p>Раздутые индексы на высокоизменяемых таблицах могут занимать в 8 раз больше места, чем нужно — планируйте REINDEX CONCURRENTLY.</p><p>В мультитенантных системах индекс по tenant_id один часто недостаточен: добавляйте диапазон времени или тип события.</p><p>Внешние ключи в PostgreSQL не индексируются автоматически — после каждого FOREIGN KEY создавайте индекс вручную.</p><p>Без EXPLAIN ANALYZE индексы создаются вслепую. Проверяйте реальный план выполнения перед деплоем.</p><h2>Ошибка 1. Индексировать всё подряд «на всякий случай»</h2><p>Самая распространённая ошибка — не недостаток индексов, а их избыток из тревожности. Особенно часто в неё попадают junior-разработчики: добавляют индекс на каждую колонку, которая встречается в WHERE, «на всякий случай». Кажется ответственным, но на самом деле вредно.</p><p>Каждый индекс — это налог на запись. При INSERT, UPDATE и DELETE PostgreSQL (или MySQL) вынуждена обновлять <b>все</b> индексы таблицы. На таблице с 8 индексами каждая запись трогает 8 структур данных. При низкой нагрузке это невидимо, но при 10 000 записей в минуту это становится узким местом.</p><p><b>Аудит неиспользуемых индексов в PostgreSQL:</b><br />Запрос к pg_stat_user_indexes покажет, сколько раз каждый индекс применялся с момента сброса статистики. Если idx_scan = 0 — индекс кандидат на удаление.</p><p>Не удаляйте индекс сразу — сначала убедитесь, что он не нужен для редких, но критичных отчётов. Но если он месяцами не используется, смело избавляйтесь.</p><h2>Ошибка 2. Не понимать селективность</h2><p>Индекс на булеву колонку — почти всегда бесполезен. <b>Селективность</b> измеряет, сколько различных значений содержится относительно общего числа строк. У boolean всего два значения. Если 95% строк имеют is_active = true, планировщик запросов проигнорирует индекс и сделает Seq Scan — и будет прав.</p><p>Правило большого пальца: если у колонки меньше 10–20 уникальных значений относительно размера таблицы, простой индекс по ней один не справится. Используйте частичные или составные индексы.</p><h2>Ошибка 3. Неверный порядок колонок в составном индексе</h2><p>Составные индексы мощны, но часто неправильно понимаются. PostgreSQL может использовать индекс (a, b, c) для фильтрации по a, a и b, a, b и c. Но <b>не может</b> эффективно использовать его, если запрос фильтрует только по b или c — ведущая колонка пропущена.</p><p>Решение: первыми ставьте колонки с equality-условиями, затем — по селективности, и проектируйте индексы вокруг <b>реальных паттернов запросов</b>, а не вокруг схемы таблицы. Также учитывайте ORDER BY и необходимость покрывающего индекса. Перед созданием обязательно запускайте EXPLAIN ANALYZE.</p><h2>Ошибка 4. Игнорировать раздутие индексов</h2><p>Индексы деградируют со временем. Многие инженеры воспринимают их как «поставил и забыл», но это заблуждение. В PostgreSQL при обновлении или удалении строки старые записи в индексе устаревают и накапливаются как <b>раздутие</b> (bloat); их физическое удаление происходит при выполнении VACUUM. На высокоизменяемых таблицах (заказы, события, логи, сессии) раздутие накапливается стремительно.</p><p>Таблица с 1 млн живых строк может иметь индекс, раздутый до размеров 8 млн записей. Каждый запрос через такой индекс делает в 8 раз больше работы, чем должен.</p><p><b>REINDEX CONCURRENTLY</b> — ключевое слово. Обычный REINDEX блокирует таблицу, а в production-SaaS это прямой путь к инциденту. CONCURRENTLY перестраивает индекс без блокировок, хотя и медленнее.</p><p>Также убедитесь, что autovacuum настроен под вашу реальную нагрузку на запись. Значения по умолчанию в PostgreSQL консервативны и часто недостаточны для SaaS с высокой интенсивностью записи.</p><h2>Ошибка 5. Индексы на колонках с малым числом уникальных значений в мультитенантных системах</h2><p>В мультитенантной архитектуре почти каждый запрос фильтрует по tenant_id. Естественное желание — проиндексировать эту колонку. Но для крупных тенантов индекс по tenant_id вернёт слишком много строк, и планировщик предпочтёт Seq Scan. Для маленьких тенантов отдельный индекс по tenant_id может быть полезен.</p><p>На серьёзном масштабе правильное решение — партиционирование таблиц по tenant_id, но это архитектурное решение. Практический первый шаг — составные индексы с временными диапазонами.</p><h2>Ошибка 6. Не индексировать внешние ключи</h2><p>В PostgreSQL внешние ключи <b>не индексируются автоматически</b>. При удалении родительской строки СУБД должна проверить все дочерние таблицы на наличие ссылающихся записей — и без индекса на внешнем ключе это Seq Scan по каждой дочерней таблице. На таблице orders с 50 млн строк удаление пользователя вызывает полное сканирование.</p><p>Сделайте это командным соглашением: в чек-листе миграций обязательный пункт «после каждого FOREIGN KEY — CREATE INDEX».</p><h2>Ошибка 7. Не использовать EXPLAIN ANALYZE перед деплоем</h2><p>Большинство решений об индексах принимаются интуитивно. Интуиция ошибается достаточно часто, чтобы это стало проблемой. EXPLAIN ANALYZE показывает, что именно делает планировщик: какие индексы использует, какие игнорирует, сколько строк реально прочитал против оценки, где тратится время.</p><p>На что обращать внимание:<br />• <b>Seq Scan</b> на большой таблице при выборке малой доли строк — возможно, пропущен индекс или планировщик не может использовать существующий индекс.<br />• <b>Rows Removed by Filter</b> в десятки тысяч — индекс есть, но неправильные колонки или низкая селективность.<br />• <b>Buffers: shared hit=0 read=45000</b> — данные не закэшированы (cold cache), страницы читаются с диска. Для диагностики раздутия смотрите общее число буферов и сравнивайте размер индекса с ожидаемым.<br />• Высокое <b>actual time</b> — проверьте раздутие, актуальность статистики, количество буферов, дисковую подсистему и наличие блокировок. Запустите ANALYZE tablename, чтобы обновить статистику планировщика.</p><h2>Чек-лист индексации для SaaS</h2><ul><li>У каждой колонки внешнего ключа есть индекс?</li><li>Составные индексы упорядочены по селективности, а не по удобству?</li><li>Булевы и низкокардинальные фильтры используют частичные индексы вместо полных?</li><li>Вы запускали EXPLAIN ANALYZE на 10 самых медленных запросов за неделю?</li><li>Есть процесс поиска и удаления неиспользуемых индексов?</li><li>Высокоизменяемые таблицы регулярно проходят REINDEX CONCURRENTLY?</li><li>Autovacuum настроен под реальный объём записи, а не под значения по умолчанию PostgreSQL?</li><li>В мультитенантных таблицах индексы начинаются с tenant_id и включают диапазон времени?</li></ul><h2>Выводы</h2><p>Индексы — не фича производительности, которую добавляют, когда всё начинает тормозить. Это проектное решение, которое принимается вместе со схемой, и пересматривается по мере эволюции паттернов запросов. Команды, которые уверенно масштабируются, — не те, у кого больше всего индексов, а те, кто понимает, что каждый индекс стоит, что даёт и когда его пора убирать.</p><blockquote>База данных, которая быстра на 10 000 строках и быстра на 50 миллионах, — не случайность. Это результат того, что кто-то считал планирование запросов первоклассной инженерной задачей, а не рутинным дополнением.</blockquote><p>Проверьте свои самые медленные запросы этой недели — возможно, одна из этих семи ошибок уже сидит в вашей production-базе.</p><p><b>Источник и материалы:</b><br />• <a href="https://dev.to/outworktech/database-indexing-mistakes-that-kill-saas-performance-at-scale-4j8e">Database Indexing Mistakes That Kill SaaS Performance at Scale</a> — оригинальная статья OutworkTech<br />• <a href="https://www.postgresql.org/docs/current/indexes.html">PostgreSQL Index Types</a> — официальная документация<br />• <a href="https://www.postgresql.org/docs/current/sql-reindex.html">REINDEX</a> — документация по перестроению индексов</p>]]></content:encoded>
    </item>
    <item>
      <title>GPU в бизнесе: как считать окупаемость инвестиций в вычислительную мощь</title>
      <link>https://tproger.ru/articles/gpu-v-biznese--kak-schitat-okupaemost-investicij-v-vychislitelnuyu-moshh</link>
      <comments>https://tproger.ru/articles/gpu-v-biznese--kak-schitat-okupaemost-investicij-v-vychislitelnuyu-moshh?scrollTo=tproger-comments</comments>
      <dc:creator><![CDATA[Алексей Филатов]]></dc:creator>
      <guid isPermaLink="true">https://tproger.ru/articles/gpu-v-biznese--kak-schitat-okupaemost-investicij-v-vychislitelnuyu-moshh</guid>
      <description><![CDATA[<p>GPU для бизнеса: окупаемость и выбор стратегии. Расчет ROI, сравнение CAPEX vs OPEX для AI, рендеринга и HPC. Аренда мощностей NVIDIA V100/P100.</p><p>— Читать дальше «<a rel="follow" href="https://tproger.ru/articles/gpu-v-biznese--kak-schitat-okupaemost-investicij-v-vychislitelnuyu-moshh">GPU в бизнесе: как считать окупаемость инвестиций в вычислительную мощь</a>»</p>]]></description>
      <category><![CDATA[Масштабируемость и ограничения памяти]]></category>
      <category><![CDATA[Оптимизация]]></category>
      <category><![CDATA[DevOps]]></category>
      <category><![CDATA[Бизнес]]></category>
      <category><![CDATA[Статьи]]></category>
      <slash:comments>0</slash:comments>
      <pubDate>Sat, 04 Oct 2025 12:00:03 GMT</pubDate>
      <content:encoded><![CDATA[<p>Бывает, команда data scientists ждёт результатов обучения модели несколько дней. А значит, мощности простаивают, идеи себя не оправдывают и всё это выливается в упущенную выгоду. Однако процесс можно сократить до нескольких часов. Звучит как магия? Нет — это реальность, которую предлагают современные графические процессоры, GPU. Но когда эта мощь перестаёт быть дорогой игрушкой и начинает приносить измеримую прибыль?</p><p>Пора перестать смотреть на GPU как на экзотику для геймеров и IT-гигантов. Сегодня это — стратегический актив. Он существенно ускорит ключевые бизнес-процессы. Вопрос лишь в том, как грамотно подойти к вложениям и не слить бюджет.</p><h2>Тревожные звоночки: пора покупать GPU?</h2><p>Как понять, что ваш бизнес достиг той точки, когда мощности центральных процессоров стали тормозом? Есть несколько явных сигналов.</p><p>Если рендеринг видео или сложной 3D-графики растягивается на ночь, заставляя дизайнеров и инженеров простаивать в ожидании. Если модели машинного обучения обучаются неделями, не позволяя оперативно тестировать гипотезы и улучшать продукт. Если анализ больших данных или сложные финансовые симуляции не дают инсайтов для принятия решений. Это не просто технические неудобства — это прямые потери денег и конкурентных позиций. Компании, которые уже используют GPU-ускорение, попросту двигаются быстрее.</p><h3>Где GPU приносят реальные деньги</h3><p>Мощность сама по себе бессмысленна. Ценность появляется, когда она решает конкретные бизнес-задачи.</p><p>Возьмём, к примеру, искусственный интеллект. Скорость здесь — это всё. Сокращение времени обучения модели с десяти дней до одного означает не просто экономию времени. Это возможность запустить на 30% больше экспериментов и быстрее выйти на рынок с уникальным продуктом. Выгода становится очевидна.</p><p>В индустрии дизайна и медиа GPU творят чудеса. Студия, сократившая время рендера кадра с часа до пяти минут, увеличивает свою пропускную способность в разы. Это позволяет брать больше проектов без расширения штата, повышать качество картинки и соблюдать самые сжатые сроки. Здесь окупаемость считается в дополнительных контрактах и лояльности клиентов.</p><h2>CAPEX или OPEX: финансовая развилка</h2><p>Итак, решение принято. Но как поступить — построить собственную инфраструктуру или воспользоваться облачной арендой? Это фундаментальный выбор между двумя финансовыми моделями.</p><p><b>Покупать свои GPU (CAPEX).</b> Это стратегия основателя крепости. Вы владеете всем: от процессора до системы охлаждения. Плюсы? Полная независимость и предсказуемые расходы в будущем. Минусы? Огромный первоначальный взнос, а техника имеет привычку устаревать буквально на глазах. Плюс к этому — штат инженеров для ее содержания. Выбор для тех, у кого работа кипит 24/7 и планы расписаны на годы вперед.</p><p><b>Арендовать в облаке (OPEX)</b>. Это стратегия мобильной команды. Нужны мощности — включил. Закончил проект — выключил и перестал платить. Главный козырь — невероятная гибкость. Сегодня тестируете модель на одной видеокарте, а завтра для нового клиента разворачиваете целый кластер за считанные минуты.</p><h2>Считаем выгоду: простая формула ROI</h2><p>Чтобы принять взвешенное решение, необходим хотя бы примерный расчет возврата на инвестиции. Формула выглядит пугающе только на первый взгляд.</p><p>Выгода (₽) = (Сэкономленное время сотрудников × почасовую ставку) + (Ускорение вывода продукта × потенциальный доход) + Снижение операционных издержек.</p><p>Затраты (₽) = Стоимость оборудования / аренды + Электроэнергия + Обслуживание.</p><p>ROI (%) = ((Доход – Затраты) / Затраты) × 100.</p><p>Рассмотрим гипотетический пример. Допустим, инженерная команда тратила на симуляции 200 часов в месяц. Внедрение GPU сократило это время до 20 часов. При стоимости часа работы инженера в $50 экономия на фонде оплаты труда составляет $9000 в месяц. Если аренда необходимых GPU-мощностей обходится в $4000, ежемесячная чистая выгода — $5000. Простой ROI в этом случае превышает 120%. Цифры условны, но они наглядно демонстрируют, что при правильном подходе инвестиции могут окупаться очень быстро.</p><h2>Резюмируя: шаг к осознанным инвестициям</h2><p>GPU давно перестали быть узкопрофильной технологией. Они превратились в мощный бизнес-инструмент и дают конкурентное преимущество через скорость. Инвестиции в них оправданы. Но важно чётко понимать, какие процессы ускорятся и как это повлияет на финансовые результаты.</p><p>Выбирайте строительство собственного кластера (CAPEX) для стабильных, непрерывных рабочих нагрузок. Аренда в облаке (OPEX) — более гибкий и менее рискованный путь для старта и тестирования идей. Начните с пилотного проекта, соберите данные и посчитайте. Возможно, ваша главная победа над конкурентами лежит не в найме еще одного сотрудника, а в покупке или аренде нескольких видеокарт.</p><h2>Чек-лист для принятия решения:</h2><ul><li>Выявлен бизнес-процесс, который тормозит развитие (AI, рендеринг, симуляции).</li><li>Измерены текущие временные и финансовые потери.</li><li>Оценена потенциальная выгода от ускорения.</li><li>Проанализирован характер нагрузки (постоянная/пиковая).</li><li>Проведено сравнение CAPEX и OPEX на основе реалистичных расчетов ROI.</li></ul>]]></content:encoded>
    </item>
    <item>
      <title>Что такое ELK-стек и как он помогает в логировании и мониторинге приложений</title>
      <link>https://tproger.ru/articles/primenenie-elk-steka-dlya-logirovaniya-i-monitoringa-prilozhenij</link>
      <comments>https://tproger.ru/articles/primenenie-elk-steka-dlya-logirovaniya-i-monitoringa-prilozhenij?scrollTo=tproger-comments</comments>
      <dc:creator><![CDATA[Денис Кудерин]]></dc:creator>
      <guid isPermaLink="true">https://tproger.ru/articles/primenenie-elk-steka-dlya-logirovaniya-i-monitoringa-prilozhenij</guid>
      <description><![CDATA[<p>Что такое ELK-стек. Зачем это нужно для логирования и мониторинга приложений. Рассматриваем основные компоненты и примеры ✔ Tproger</p><p>— Читать дальше «<a rel="follow" href="https://tproger.ru/articles/primenenie-elk-steka-dlya-logirovaniya-i-monitoringa-prilozhenij">Что такое ELK-стек и как он помогает в логировании и мониторинге приложений</a>»</p>]]></description>
      <category><![CDATA[Масштабируемость и ограничения памяти]]></category>
      <category><![CDATA[Производительность]]></category>
      <category><![CDATA[Статьи]]></category>
      <slash:comments>0</slash:comments>
      <pubDate>Tue, 10 Dec 2024 10:00:03 GMT</pubDate>
      <content:encoded><![CDATA[<p>Стек ELK — набор компонентов, который обеспечивает эффективное и удобное централизованное логирование с нескольких серверов. Логирование здесь — ведение журналов.</p><p>ELK — аббревиатура от Elasticsearch, Logstash, Kibana. Это названия опенсорс-проектов, которые разрабатываются и поддерживаются компанией Elastic.</p><p>Стек ELK позволяет быстро и безопасно извлекать данные в нужном формате из любых источников и работать с этой информацией в режиме поиска, анализа или визуализации в режиме реального времени. Специалисты часто называют стек «эластичным» — небольшая пасхалочка.</p><p>Зачастую журналы программных сервисов и приложений оказываются единственным средством, с помощью которого можно найти источник проблем, однако после монтажа и настройки о журналах просто забывают. Такой подход в современной разработке недопустим: развертывание и сопровождение продуктов требуют более ответственного отношения — нужны опции для мгновенного поиска нужных данных. С этой задачей системы сбора и анализа журналов справляются максимально эффективно.</p><p>Узнаем, что собой представляет ELK-стек, как он работает, каковы его преимущества и как правильно настроить настроить и оптимизировать этот программный инструмент.</p><h2>Из чего состоит ELK-стека</h2><p>Существуют разные способы формирования структуры данных в памяти устройства, стек — один из основных вариантов. В таких системах используется последовательный способ хранения с линейной связью. Данные хранятся в строгом порядке, и эту последовательность нельзя нарушить.</p><p>Извлечение данных происходит по принципу «пришел последним, вышел первым» по аналогии с пассажирами в вагонах метро — кто заходит позже других, вынужден выходить на остановке первым. По такому принципу работают все стеки.</p><p>Программный продукт ELK-стек — это связка из приложений Elasticsearch, Logstash, Kibana, а также Filebeat (доставщик лог-сообщений, отвечающий за мониторинг и сбор данных и их пересылку).</p><p>С помощью стека разработчики без труда собирают журналы всех систем и приложений, анализируют их и создают визуализацию. Это необходимо, чтобы мониторить продукты и инфраструктуру, быстро устранять неполадки, тестировать систему безопасности и проделывать другие операции с данными. ELK-стек предоставляет все необходимые сведения об обслуживаемой системе, выполняет свою работу оперативно и наглядно.</p><p>Рассмотрим в подробностях каждый из компонентов ELK.</p><h2>Elasticsearch</h2><p>Основа стека ELK. В техническом плане это распределенная RESTful-система (инструмент для взаимодействия веб-приложений и серверов) на базе JSON — текстового формата для обмена данными, созданного на JavaScript. Компонент Elasticsearch сочетает в себе функции базы данных NoSQL, поисковика и системы аналитики.</p><p>Изначально инструмент разрабатывался как поисковая утилита библиотеки Apache Lucene с дополнительными возможностями в виде репликации, масштабирования и некоторых других опций. Эта многофункциональность плюс простота применения и повышенная производительность сделали Elasticsearch крайне удобным инструментом для проектов с высокой нагрузкой, массивами данных и усложненными поисковыми системами.</p><p>Постепенно инструмент стал использоваться не только для реализации внутреннего поиска на сайтах, но и для разработки систем централизованного хранения логов и оперативной аналитики. Благодаря разнообразной функциональности решение стало востребованным во многих направлениях, что привлекло внимание разработчиков из самых разных сфер ИТ. Вокруг ES сформировалось компетентное сообщество — специалисты всегда помогут настроить кластер для решения любых задач.</p><p>Статус Elasticsearch в настоящий момент — обширное, быстродействующее, масштабируемое хранилище с функциями поисковика и анализатора журналов. Мощность, простота, документация JSON, поддержка всех актуальных языков программирования и геолокации — все эти достоинства позволяют системе в скоростном режиме работать с большими массивами данных, индексировать логи по мере их поступления и реализовывать запросы в онлайн-формате.</p><p>В коммерческих проектах разработчиков Elasticsearch решает следующие задачи:</p><ul><li>агрегирует данные любого формата и объема с различных площадок (например, информацию о товарах из множества онлайн-магазинов), выполняет фильтрацию и поиск по заданным свойствам продуктов;</li><li>шардирует (разделяет) данные и распределяет их на автономные хосты для экономии места, при этом группы автоматически выравниваются для соблюдения баланса;</li><li>обрабатывает данные из множества онлайн-опросников и анкет;</li><li>оперативно вычисляет заданные параметры в массивах данных, собранных из разных систем — такие задачи важны в комплексной аналитике бизнес-процессов;</li><li>анализирует обширные объемы неструктурированных статистических данных;</li><li>хранит информацию и управляет записью с помощью API.</li></ul><p>Можно назвать Elasticsearch распределенным поисковым движком, который используется для широкого круга задач в рамках аналитики, поиска, распределения и хранения данных. Установить его проще всего через Docker.</p><h2>Logstash</h2><p>В стеке ELK Logstash выступает в роли конвейера для парсинга — автоматизированного сбора и структурирования данных (в нашем случае логов событий), которые приходят из многих источников, с целью их обработки и последующего применения в Elasticsearch.</p><p>Эта утилита с открытым кодом позволяет выделять отдельные поля с их значениями в сообщениях о системных событиях, распределять данные с помощью фильтров и редактировать их. Настройка выполняется через конфигурационные файлы.</p><p>В системе Logstash очередность событий разделяется на три фазы:</p><ol><li><b>Input.</b> Журналы передаются для обработки в машинно-понятный формат. Можно установить последовательность чтения файлов из папок, путь и протокол для извлечения логов.</li><li><b>Filter.</b> Поле управления сообщениями, приходящими на Logstash. Здесь задан набор условий для выполнения конкретных действий или событий. Настраивается также парсер логов — можно редактировать значения, добавлять и удалять новые параметры.</li><li><b>Output. </b>Фаза настройки места, куда будет отправлен обработанный лог. Если непосредственно в Elasticsearch, потребуется документальный запрос. Если это дебаг (код для исправления ошибки), он просто записывается в файл.</li></ol><p>Задача Logstash — централизованная и оперативная обработка обширного объема событий, различных данных со структурой и без нее. Чтобы подключаться к разным способам ввода, утилита использует более 200 плагинов для упрощения доступа.</p><p>Через Logstash данные из источников поступают в Elasticsearch. По пути они могут быть преобразованы в нужный формат. Встроенные фильтры и модули обеспечивают юзерам простой и быстрый доступ к данным независимо от их типа.</p><p>На GitHub в свободном доступе находится более 20 моделей — высока вероятность, что там уже есть именно тот инструмент, который подойдет для настройки нужного вам конвейера данных. Если такого модуля нет, вы без особого труда сможете создать его самостоятельно. Как и ES, Logstash можно установить и запустить в Docker.</p><h2>Kibana</h2><p>Этот компонент представляет собой веб-панель (пользовательский интерфейс) для взаимодействия с логами. Он отвечает за визуализацию проиндексированных данных в Elasticsearch в формате графиков и разного рода диаграмм. Благодаря гибким настройкам мониторинга с помощью Kibana можно путешествовать по ELK-стеку, отслеживая маршруты, по которым запросы проходят через конкретное приложение.</p><p>Инструмент подходит для сложного анализа данных и визуализации этого процесса, а также для администрирования баз данных. Есть возможность контролировать несколько панелей мониторинга с сохранением настроек для каждой из них.</p><p>Есть несколько методов поиска по данным Кибана в режиме реал-тайм:</p><ul><li>текстовый поиск определенной строки;</li><li>поиск в формате поля;</li><li>логические матрицы для объединения нескольких поисков;</li><li>поиск близких по смыслу терминов.</li></ul><p>В Kibana интуитивно понятные схемы и аналитические отчеты, которые можно применять для интерактивной навигации. Можно динамически перемещать созданные окна, играть с масштабом подмножеств и максимально детализировать отчеты.</p><h2>Как работает ELK-стек</h2><p>Elasticsearch занимается хранением и поиском данных, Logstash обрабатывает, фильтрует и структурирует логи, в Kibana визуализирует поисковые операции и занимается администрированием.</p><figure><img src="https://media.tproger.ru/user-uploads/101078/2024-12-10/1f82c5cd-5228-422b-934b-c7b356667f81.png" alt="" /></figure><p>Все три компонента расположены на сервере пользователя, который выполняет функцию клиента. За логами следит доставщик Filebeat, он же пушит логи в Logstash. Последний компонент выполняет с логами определенные манипуляции и помещает их в нужный столбец таблицы Elasticsearch. Далее с помощью визуализации Kibana пользователь с легкостью ищет нужные события.</p><p>Функционал стека ELK довольно широк — это аналитика журналов, поиск нужных файлов, контроль информации и безопасности, наблюдение за работой приложения. Это эффективный поисково-аналитический инструмент, который удовлетворяет все потребности разработчиков, инженеров и аналитиков в сфере работы с журналами.</p><p>Поскольку значительная часть инфраструктуры в ИТ сегодня перемещена в публичные облачные структуры, разработчикам нужны действенные решения для управления журналами, с помощью которых можно мониторить структуру приложений, обрабатывать журналы и данные навигации.</p><p>Стеком пользуются не только разрабы, но и инженеры DevOps (актуальная концепция непрерывной разработки). Специалисты получают полезную информацию, выполняя диагностику сбоев, аналитику производительности софта и мониторинг инфраструктуры.</p><h2>Настройка и применение ELK-стека на практике</h2><p>Есть несколько способов установки стека ELK в разных ОС и средах. Это делается локально, в облачных сервисах, через Docker и системы управления конфигурацией. Рассмотрим универсальные этапы установки на примере ОС Linux.</p><p>Для развертывания понадобятся определенные системные ресурсы: около 10 Гб памяти и примерно столько места на жестком диске. Для Elasticsearch и Logstash потребуется Java 8. А еще нужно заранее установить агент Filebeat — сейчас он стал неотъемлемой частью экосистемы ELK.</p><p>Основные этапы развертывания:</p><ol><li><b>Установка Filebeat </b>из отдельных пакетов, репозитория или через Docker Compose.</li><li><b>Установка Elasticsearch.</b> Этот этап довольно простой и быстрый благодаря готовым пакетам — есть версии для всех востребованных операционных систем. Потребуется скачать публичный ключ репозитория и подключить Elasticsearch.</li><li><b>Настройка ES.</b> Все необходимые настройки находятся в специальном файле. Промежуточные результаты можно наблюдать на локальном интерфейсе.</li><li><b>Очистка индексов в ES.</b> Это автоматический процесс, за который отвечает встроенный инструмент Index Lifecycle Policies. Его тоже можно настроить под конкретные задачи.</li><li><b>Установка Kibana.</b> Есть пакеты и репозиторий для всех популярных платформ.</li><li><b>Настройка Kibana. </b>Все нужные параметры находятся в специальном файле. Не забудьте настроить безопасность и авторизацию.</li><li><b>Установка Logstash.</b> Компонент устанавливается из того же репозитория, что и Elasticsearch и Kibana.</li><li><b>Настройка Logstash.</b> Конфигурационные файлы для настройки находятся в соответствующих файлах. Фильтры можно расположить на свое усмотрение.</li></ol><p>В качестве наглядного примера использования стека ELK можно привести банковские сервисы, которые делают по требованию клиентов выписки по счету в личных онлайн-кабинетах. Пользователи могут выполнить полнотекстовый гибкий поиск по транзакциям. Такая опция реализована, в частности в веб-приложении Альфа-Банке.</p><p>ELK используют и многие другие компании, среди которых Mozilla, GitHub и Netflix. Везде, где нужно работать с Big Data, разработчики отдают предпочтение Elastic, поскольку он закрывает большинство потребностей по поиску, обработке и аналитике.</p><h2>В чем преимущества использования ELK для мониторинга</h2><p>В цифровом мире данные отвечают за всё, и их становится все больше. Это значит, что решения для обработки данных должны постоянно эволюционировать. В такой ситуации стек ELK делает работу с данными проще и улучшает качество аналитики.</p><p>Перечислим все плюсы ELK:</p><ul><li>способен быстро и без последствий для системы извлекать, обрабатывать, анализировать данные и визуализировать их независимо от формата;</li><li>позволяет вести журнал из одного центра, отслеживать все проблемы в приложении и на серверах, сравнивать журналы на разных серверах и делать выводы;</li><li>прост и удобен в настройке и использовании, доступен для освоения начинающим ИТ-специалистам;</li><li>в набор компонентов входят программные продукты с открытым исходным кодом;</li><li>предоставляет гибкие поисковые фильтры, в том числе для работы с азиатскими языками;</li><li>встроенные преобразователи текста выполняют токенизацию, леммматизацию и другие преобразования контента в соответствии с задачами пользователя;</li><li>стек можно развернуть в любых масштабах независимо от технической структуры компании;</li><li>для решения характерна стабильность, отказоустойчивость и надежность — данные при сборе не теряются и не исчезают;</li><li>большой выбор настроек под конкретные задачи проекта;</li><li>есть официальные клиенты с профессиональной поддержкой, предоставленные разработчиками на востребованных языках программирования;</li><li>стек универсален и подходит для работы с СУБД, файлами любого типа, приложениями и другими данными;</li><li>контейнер для загрузки компонентов стека есть на Docker.</li></ul><p>Но эксперты отмечают и недостатки:</p><ul><li>В ELK нет встроенной опции авторизации с управлением правами доступа. Это потенциальная проблема с информационной безопасностью. Базу данных нужно в обязательном порядке защищать паролем, поскольку движок по умолчанию можно открыть через любые интерфейсы.</li><li>Масштабирование в некоторых проектах требует значительных ресурсов и может оказаться нерентабельным.</li><li>Размеры индексов могут превысить лимиты хранилища узла, индексация начинает сбоить — есть риск потери данных.</li></ul><p>При этом установка и грамотный запуск стека — довольно ответственный и непростой процесс. Организациям, у которых нет собственных специалистов, придется нанять сотрудника со стороны, который будет управлять развертыванием.</p><h2>Итоги</h2><p>Годы практики и активное комьюнити создали из ELK перспективный многофункциональный инструмент, у которого нет достойной альтернативы. Стек ELK широко используется для мониторинга производительности приложений, поиска аномалий и ошибок в системе, выяснения причин багов и дефектов с целью их устранения на программном уровне.</p><p>Это современное решение позволяет инженерам и разработчикам настраивать мониторинг динамических сред с распределением и выполнять профессиональную аналитику. ELK — мощная платформа, которая умеет собирать данные из разных источников, помещать в централизованное хранилище и обрабатывать. По мере роста базы систему легко масштабировать, а многие трудности при настройке решаются через привлечение активного и доброжелательного сообщества.</p>]]></content:encoded>
    </item>
    <item>
      <title>Как работает виртуальная память в операционных системах</title>
      <link>https://tproger.ru/articles/kak-rabotaet-virtualnaya-pamyat-v-operacionnyh-sistemah</link>
      <comments>https://tproger.ru/articles/kak-rabotaet-virtualnaya-pamyat-v-operacionnyh-sistemah?scrollTo=tproger-comments</comments>
      <dc:creator><![CDATA[Вадим Егорцев]]></dc:creator>
      <guid isPermaLink="true">https://tproger.ru/articles/kak-rabotaet-virtualnaya-pamyat-v-operacionnyh-sistemah</guid>
      <description><![CDATA[<p>Как работает виртуальная память в операционных системах. Показываем ключевые инструменты и особенности работы с виртуальной памятью. Рассматриваем основные нюансы ✔ Tproger</p><p>— Читать дальше «<a rel="follow" href="https://tproger.ru/articles/kak-rabotaet-virtualnaya-pamyat-v-operacionnyh-sistemah">Как работает виртуальная память в операционных системах</a>»</p>]]></description>
      <category><![CDATA[Масштабируемость и ограничения памяти]]></category>
      <category><![CDATA[Статьи]]></category>
      <slash:comments>0</slash:comments>
      <pubDate>Thu, 28 Nov 2024 10:00:03 GMT</pubDate>
      <content:encoded><![CDATA[<p>Виртуальная память распределяет ограниченные ресурсы оперативной памяти между процессами. Она не только обеспечивает бесперебойную работу компьютера, но и создает для каждой программы иллюзию неограниченного пространства, защищая процессы друг от друга и предотвращая сбои системы.</p><h2>Основные концепции виртуальной памяти</h2><p><b>Оперативная (физическая) память</b> <b>компьютера </b>— это реальное хранилище, где размещаются исполняемый код и данные активных процессов. Однако использование физической памяти сопряжено с ограничениями:</p><ul><li>Размер программы не должен превышать доступную физическую память.</li><li>Нужно контролировать, чтобы адресное пространство запускаемой программы не пересекалось с работающими процессами.</li><li>Нужно изолировать каждый процесс от несанкционированного доступа других программ.</li></ul><p>Механизм <b>виртуальной памяти</b> решает перечисленные проблемы. Каждому процессу предоставляется <b>виртуальное адресное пространство</b>. При этом программа оперирует только виртуальными адресами, будучи полностью изолированной от физического размещения данных.</p><p>Такой подход обеспечивает 3 ключевых преимущества:</p><ul><li>Процессы могут работать с объемом данных, превышающим физическую память компьютера.</li><li>Процессы работают в отдельных виртуальных участках памяти, что предотвращает вмешательство одного процесса в память другого.</li><li>ОС может контролировать доступ к отдельным страницам памяти, предотвращая несанкционированный доступ.</li></ul><p>Каждый процесс имеет собственное виртуальное адресное пространство. Оно разделено на фиксированные блоки данных — <b>страницы</b>. Их размер 4 КБ или 8 КБ в зависимости от архитектуры системы. Физическая память также разделена на блоки того же размера — <b>фреймы</b>.</p><figure><img src="https://media.tproger.ru/user-uploads/105601/2024-11-22/b6e54659-a7c1-4471-a923-b73079b4b648.jpg" alt="" /></figure><p>Когда процессу требуется доступ к данным, он обращается к виртуальному адресу. ОС преобразует виртуальный адрес в физический, используя <b>таблицу страниц</b>. Если запрашиваемые данные отсутствуют в физической памяти, они загружаются с диска в оперативную память.</p><p><b>Страничный файл</b> (файл подкачки или swap file) — это область на жестком диске, которая используется для хранения страниц виртуальной памяти, когда они не помещаются в физическую память. С помощью файла подкачки ОС освобождает физическую память, перемещая неактивные страницы на диск, и загружает их обратно при необходимости.</p><h2>Механизм работы виртуальной памяти</h2><figure><img src="https://media.tproger.ru/user-uploads/105601/2024-11-22/b498a1c0-1185-458b-bcbd-7b9362934863.jpg" alt="" /></figure><p>Процесс преобразования виртуальных адресов в физические состоит из следующих этапов:</p><ol><li><b>Разделение памяти</b>. Как виртуальное адресное пространство, так и физическая память разделены на страницы и фреймы соответственно.</li><li><b>Таблица страниц</b>. Для каждого процесса создается таблица страниц, которая содержит информацию о соответствии между виртуальными страницами и физическими фреймами.</li><li><b>Преобразование адресов</b>. Когда процесс обращается к виртуальному адресу, ОС использует таблицу страниц для определения соответствующего физического адреса.</li><li><b>Обработка страничных ошибок</b>. Если запрашиваемая страница отсутствует в физической памяти, возникает страничная ошибка (page fault). ОС загружает требуемую страницу с диска в память.</li><li><b>Обновление таблицы страниц</b>. После загрузки страницы таблица обновляется, чтобы отразить новое местоположение данных.</li></ol><p>Для оптимизации процесса преобразования адресов используется аппаратное устройство — Translation Lookaside Buffer. TLB представляет собой кэш, который хранит недавно использованные преобразования виртуальных адресов в физические, что значительно ускоряет доступ к памяти.</p><h2>Алгоритмы управления виртуальной памятью</h2><p>Когда физическая память заполнена и требуется загрузить новую страницу, ОС должна решить, какую страницу выгрузить на диск. Для принятия решения используются алгоритмы:</p><ul><li><b>FIFO</b>. Выгружает страницу, находящуюся в памяти дольше всего. FIFO не учитывает частоту использования страниц.</li><li><b>LRU</b>. Выгружает страницу, которая не использовалась дольше всего. LRU эффективнее FIFO, но требует больше ресурсов для отслеживания времени последнего использования каждой страницы.</li><li><b>LFU</b>. Выгружается страница, которая использовалась реже всего. LFU необходим, когда к данным обращаются часто, поэтому их желательно хранить в кэше.</li><li><b>Clock</b>. Похож на алгоритм LRU. Использует бит доступа для каждой страницы и циклически проверяет страницы, сбрасывая бит доступа, пока не найдет страницу для выгрузки.</li></ul><p>Помимо алгоритмов замещения, для оптимизации использования виртуальной памяти используются алгоритмы предсказания. Они основаны на том, что процессы имеют тенденцию обращаться к ограниченному набору страниц в течение коротких периодов времени. Так система понимает, какие страницы загружать предварительно, так как они могут потребоваться в ближайшем будущем.</p><p>Когда процесс пытается получить доступ к странице, которой нет в физической памяти, возникает <b>страничная ошибка</b>. ОС должна обработать это исключение, загрузить требуемую страницу с диска и возобновить выполнение процесса.</p><p>Чтобы сократить количество страничных ошибок, используются следующие техники:</p><ul><li><b>Предварительная подкачка</b>. ОС пытается предугадать, какие страницы понадобятся процессу в ближайшем будущем, и загружает их заранее.</li><li><b>Группировка страниц</b>. При загрузке страницы с диска система загружает несколько соседних страниц, предполагая, что они также потребуются в ближайшем будущем.</li><li><b>Адаптивное управление рабочим набором</b>. Система отслеживает набор страниц, активно используемых процессом, и старается удерживать его в памяти для минимизации страничных ошибок.</li></ul><h2>Роль ОС в управлении виртуальной памятью</h2><p>Операционная система управляет виртуальной памятью и отвечает за:</p><ul><li>Создание и поддержание таблиц страниц для каждого процесса.</li><li>Управление физической памятью и страничным файлом.</li><li>Обработку страничных ошибок и загрузку страниц с диска.</li><li>Реализацию алгоритмов замещения страниц.</li><li>Оптимизацию использования памяти и минимизацию фрагментации.</li><li>Изоляцию и защиту памяти между процессами.</li></ul><p>Реализация виртуальной памяти различается в разных ОС.</p><ul><li><b>Windows</b> использует файл подкачки (pagefile.sys), размер которого может динамически изменяться. Windows также поддерживает механизм приоритетного ввода/вывода для страниц виртуальной памяти.</li><li><b>Linux</b> использует выделенный раздел диска (swap partition) или файл подкачки (swap file) для виртуальной памяти. Linux предоставляет гибкие настройки для управления swap-пространством и позволяет использовать несколько swap-устройств с разными приоритетами.</li><li><b>MacOS</b> использует механизм компрессии, который сжимает неиспользуемые страницы в памяти перед их выгрузкой на диск. Это уменьшает количество операций ввода/вывода.</li></ul><p>Операционная система также отвечает за очистку и освобождение хранилища:</p><ul><li>Периодически сканирует память для идентификации и освобождения неиспользуемых страниц.</li><li>Дефрагментирует память для оптимизации физических ресурсов.</li><li>Управление кэшем файловой системы, который использует свободную физическую память для ускорения доступа к файлам.</li><li>Балансировку между использованием физической памяти и страничного файла для производительности системы.</li></ul><h2>Преимущества и недостатки виртуальной памяти</h2><p>Плюсы:</p><ul><li><b>Многозадачность</b>. Одновременно выполняется больше программ, чем позволяет физическая память.</li><li><b>Эффективное использование памяти</b>. Только активно используемые данные хранятся в физической памяти.</li><li><b>Безопасность</b>. Изоляция процессов и контроль доступа к памяти повышают общую безопасность системы.</li><li><b>Упрощение разработки ПО</b>. Разработчики создают программы, не учитывая физические ограничения памяти конкретного устройства.</li><li><b>Поддержка больших адресных пространств</b>. Позволяет процессам работать с объемами данных, превышающими физическую память.</li></ul><p>Минусы:</p><ul><li><b>Снижение производительности при чрезмерном использовании swap</b>. Когда система часто обращается к страничному файлу, это значительно замедляет работу из-за низкой скорости дисковых операций по сравнению с оперативной памятью.</li><li><b>Проблемы с фрагментацией</b>. Со временем может возникнуть фрагментация как физических, так и виртуальных участков памяти, что снижает эффективность использования ресурсов.</li><li><b>Сложность настройки</b>. Настройка виртуальной памяти — это непростая задача, особенно в системах с ограниченными ресурсами.</li><li><b>Увеличение износа диска</b>. Интенсивное использование виртуальной памяти может привести к ускоренному износу накопителя из-за большого количества операций записи.</li></ul><p>Виртуальная память может стать узким местом производительности системы в следующих случаях:</p><ul><li><b>Недостаточный объем физической памяти</b>. Если физической памяти недостаточно для хранения рабочих наборов активных процессов, система будет часто обращаться к страничному файлу, что приведет к значительному снижению производительности.</li><li><b>Интенсивные операции с большими объемами данных</b>. Приложения, работающие с большими наборами данных, могут вызвать частые страничные ошибки, если их рабочий набор не помещается в физическую память.</li><li><b>Неоптимальные настройки</b>. Неправильно настроенный размер страничного файла или неэффективные алгоритмы управления памятью могут привести к снижению производительности.</li><li><b>Фрагментация памяти</b>. Сильная фрагментация может привести к неэффективному использованию как физической, так и виртуальной памяти.</li></ul><h2>Советы по оптимизации виртуальной памяти</h2><p><b>Файл подкачки</b> — основной компонент виртуальной памяти в Windows. Он расположен в корневой директории жесткого диска. Системный файл pagefile.sys выступает резервным хранилищем данных. Оно активируется при недостатке оперативной памяти для предотвращения сбоев в работе приложений.</p><p>В Windows 8-11 есть взаимосвязанные системные файлы: <b>pagefile.sys</b> и <b>swapfile.sys</b>. Первый отвечает за общее управление виртуальной памятью, второй обслуживает приложения из Microsoft Store. При полном отключении файла подкачки оба компонента удаляются из файловой системы.</p><p>Доступ к настройкам файла подкачки возможен через системные интерфейсы. Можно воспользоваться встроенным поиском Windows, утилитой «Выполнить» или стандартным меню «Пуск».</p><figure><img src="https://media.tproger.ru/user-uploads/105601/2024-11-22/a40b4f31-9169-42ef-9746-6f3b54fc1b90.jpg" alt="" /><figcaption>Чтобы перейти к настройкам файла подкачки Windows, нужно нажать комбинацию Win + R и ввести команду sysdm.cpi</figcaption></figure><p>Можно корректировать размер файла подкачки, перемещать его между дисками или полностью отключить. Рекомендуется не вмешиваться в настройки страничного файла. <b>Операционная система самостоятельно определяет оптимальные режимы работы.</b></p><p>Объем виртуальной памяти напрямую зависит от физических характеристик оперативной памяти, но не является их прямой пропорцией. Некорректное уменьшение или отключение файла подкачки может привести к нестабильной работе компьютера.</p><p>Лучшим решением будет перемещение файла подкачки на твердотельный накопитель. Скорость чтения и записи SSD-дисков оптимизирует систему при работе с виртуальной памятью. Риски преждевременного износа накопителя при таком подходе минимальны.</p><p><b>Не рекомендуется полностью отключать файл подкачки</b>. В большинстве сценариев такой подход провоцирует сбои в работе ОС. Оптимальная стратегия работы с файлом подкачки — сохранение настроек по умолчанию.</p><h2>Настройка виртуальной памяти в Linux</h2><p>Настройка пространства подкачки в Linux осуществляется двумя основными методами: через выделенный раздел диска и создание файла подкачки.</p><p>Процесс формирования раздела подкачки начинается на этапе установки операционной системы. Программы установки Linux автоматически определяют аппаратную конфигурацию и предлагают оптимальную схему разметки диска, включающую раздел подкачки.</p><p>При необходимости можно добавить раздел подкачки после завершения первоначальной инсталляции. Для этого применяются утилиты неразрушающего форматирования дисков, например, GParted. Принципиально важно выбрать файловую систему типа «Linux swap».</p><p>Технология подготовки раздела подкачки включает несколько последовательных шагов:</p><ol><li>Форматирование раздела с помощью утилиты mkswap.</li><li>Редактирование системного файла /etc/fstab для автоматического монтирования при загрузке.</li><li>Активация пространства подкачки командой swapon.</li></ol><h4>Альтернативный подход — файлы подкачки</h4><p>Файловый механизм подкачки представляет собой более гибкий инструмент конфигурирования виртуальной памяти. Преимущества данного метода проявляются при работе в следующих сценариях:</p><ul><li>Использование виртуальных машин.</li><li>Работа со встраиваемыми системами.</li></ul><p>Создание файла подкачки реализуется через несколько подходов:</p><ul><li>Метод с использованием утилиты fallocate:</li></ul><p>sudo fallocate -l 2G /path/to/swapfile</p><ul><li>Метод с применением команды dd:</li></ul><p>dd if=/dev/zero of=swapfile bs=1024k count=2048</p><h2>Планирование объема виртуальной памяти Linux</h2><p>Настройка поведения подкачки осуществляется через параметр ядра «swappiness». Шкала значений варьируется от 0 до 100:</p><ul><li>0 — полное торможение использования swap;</li><li>60 — штатное поведение большинства дистрибутивов;</li><li>100 — максимальное применение виртуальной памяти.</li></ul><p>Временное изменение параметров производится командой:</p><p>sudo sysctl vm.swappiness=20</p><p>Постоянная конфигурация реализуется правкой файла /etc/sysctl.conf путем внесения записи вида:</p><p>sudo vm.swappiness=20</p><h2>Что запомнить?</h2><ul><li>Виртуальная память решает проблемы ограничений физической памяти, изоляции процессов и контроля доступа.</li><li>Каждый процесс имеет собственное виртуальное адресное пространство, разделенное на страницы.</li><li>ОС преобразует виртуальные адреса в физические с помощью таблицы страниц.</li><li>Страничный файл используется для хранения неактивных страниц на диске.</li><li>Процесс преобразования адресов включает разделение памяти, использование таблицы страниц, обработку страничных ошибок.</li><li>ОС отвечает за создание таблиц страниц, управление памятью, обработку ошибок, реализацию алгоритмов замещения: FIFO, LRU, LFU, Clock.</li><li>Параметры виртуальной памяти можно изменять.</li><li>Система по умолчанию выбирает оптимальный режим работы исходя из конфигурации компьютера.</li></ul>]]></content:encoded>
    </item>
    <item>
      <title>Распределение памяти в Python: сколько и в каких случаях занимают типы данных</title>
      <link>https://tproger.ru/articles/raspredelenie-pamjati-v-python-skolko-i-v-kakih-sluchajah-zanimajut-tipy-dannyh</link>
      <comments>https://tproger.ru/articles/raspredelenie-pamjati-v-python-skolko-i-v-kakih-sluchajah-zanimajut-tipy-dannyh?scrollTo=tproger-comments</comments>
      <dc:creator><![CDATA[Илья Латышев]]></dc:creator>
      <guid isPermaLink="true">https://tproger.ru/articles/raspredelenie-pamjati-v-python-skolko-i-v-kakih-sluchajah-zanimajut-tipy-dannyh</guid>
      <description><![CDATA[<p>Как устроено выделение памяти под объекты в Python, как работает очистка памяти и в чём разница в памяти на примере типов list, dict и tuple.</p><p>— Читать дальше «<a rel="follow" href="https://tproger.ru/articles/raspredelenie-pamjati-v-python-skolko-i-v-kakih-sluchajah-zanimajut-tipy-dannyh">Распределение памяти в Python: сколько и в каких случаях занимают типы данных</a>»</p>]]></description>
      <category><![CDATA[Масштабируемость и ограничения памяти]]></category>
      <category><![CDATA[Python]]></category>
      <category><![CDATA[Гостевая публикация]]></category>
      <category><![CDATA[Статьи]]></category>
      <slash:comments>0</slash:comments>
      <pubDate>Mon, 07 Nov 2022 12:19:12 GMT</pubDate>
      <content:encoded><![CDATA[<p>Идея статьи возникла после просмотра одного видео, где автор разбирает различные способы создания списка из одинаковых элементов. Меня заинтересовала эта тема, и я начал углубляться в нее. В частности, почему в том или ином случае <a href="https://tproger.ru/articles/lajfhaki-python-sjekonomit-pamjat-i-uskorit-vypolnenie-programmy/">объем занимаемой памяти отличается</a>.</p><p>В этом материале разберем, как устроено выделение памяти под объекты в Python. Потом кратко о том, как работает очистка памяти от неиспользуемых объектов. И, наконец, о разнице в занимаемой памяти на примере типов list, dict и tuple. Эта статья — часть нашего <a href="https://tproger.ru/articles/python--polnyj-putevoditel-dlya-razrabotchika">полного путеводителя по Python</a>. Эта статья — часть нашего <a href="https://tproger.ru/articles/python--polnyj-putevoditel-dlya-razrabotchika">полного путеводителя по Python</a>.</p><h2>Выделение памяти</h2><p>Напрямую из кода память не выделяется. Вся работа по выделению памяти перекладывается на менеджеров памяти. Есть один общий менеджер «верхнего» уровня, который отвечает за выделение большого блока из выделенной программе памяти — <b>«арена»</b>. Занимает 256Кб.</p><p>Далее арена делится на <b>«пулы»</b> по 4Кб. Каждый пул может содержать в себе только блоки заранее определенного для этого пула размера — от 8 байт до 512 байт. Арена может содержать в себе пулы разных размеров, а вот блоки в одном пуле всегда одного размера. И вот на уровне блоков работают менеджеры памяти каждого конкретного типа данных.</p><p>Когда менеджер определенного типа запрашивает память для объекта, он заранее знает размер и может сразу обратиться к пулу с нужным размером блоков, разместив объект в первом свободном блоке. Если же свободных блоков нет или же пулов нужного размера нет, верхний менеджер выдает новый пул из наиболее заполненной арены. Если и все арены заняты, запрашивается новая арена.</p><p>Интересно, что частично вернуть выделенную под арену память нельзя, пока в ней есть хоть один непустой пул, в котором есть хоть один непустой блок. Как блоки становятся пустыми, мы обсудим в следующем разделе.</p><h2>Освобождение памяти</h2><p>В Python нет необходимости в ручной очистке памяти. Если объект больше не используется, все это перекладывается на сам интерпретатор и два механизма: <b>счетчик ссылок</b> и <b>сборщик мусора</b>.</p><p>Когда переменной присваивается значение, на самом деле сначала создается объект в подходящем свободном блоке (как работает выделение памяти разобрались в прошлом разделе) и потом уже в переменную кладется ссылка на этот объект.</p><h3>Счетчик ссылок</h3><p>Каждый созданный объект имеет специальное поле — счетчик ссылок. Он хранит в себе количество ссылающихся на него объектов. Увеличивает свое значение, например, когда используется операция присваивания, или когда объект становится частью списка. При удалении переменной или же при использовании del счетчик ссылок уменьшается на 1. Например, при завершении работы функции, где эта переменная была объявлена.</p><p>Разберем на примере небольшой кусок кода:</p><p>В данном случае 1000 — это неизменяемый объект, один на всю программу. После инициализации двух переменных счетчик ссылок равен 5.</p><p>Почему 5? Потому что на объект 1000 ссылаются не только эти две переменные, а все переменные со значением 1000 во всех используемых модулях. Далее удаляем переменную b и счетчик ссылок меняет свое значение на 4. Как только счетчик достигает 0, объект освобождает блок.</p><h3>Сборщик мусора</h3><p>У счетчика ссылок есть большой недостаток — невозможность отловить циклические ссылки. Если объект или несколько объектов ссылаются друг на друга, счетчик никогда не опустится ниже 1.</p><p>Специально для обработки таких случаев был создан <a href="https://docs.python.org/3/library/gc.html">модуль gc</a>. Его работа заключается в том, чтобы периодически сканировать объекты контейнерного типа и определять наличие циклических ссылок.</p><p>Говоря о сборщике мусора, важным понятием является <b>поколение</b> объектов — это набор объектов, за которыми следит, а в последующем сканирует сборщик. Есть три поколения, каждое из которых сканируется с разной частотой. Чаще сканируются объекты первого поколения, т.к. туда попадают новые объекты. Как правило, такие объекты имеют маленький срок жизни и являются временными. Поэтому целесообразно их проверять больше. Если объекты прошли сканирование сборщика мусора и не были удалены, то переходят в следующее поколение.</p><p>Сканирование первого поколения начинается, когда количество созданных объектов контейнерного типа превышает количество удаленных на заданный <b>порог</b>. Например, сканирование второго поколения начнется, когда количество сканирований первого поколения превысит заданный порог. По умолчанию, пороги срабатывания — это 700, 10 и 10, соответственно.</p><p>Посмотреть их можно через <i>gc.get_threshold()</i>. Изменить через <i>gc.set_threshold()</i>.</p><p>Все это актуально для list, dict, tuple и еще для классов. Не для простых типов. Базовый обзор типов данных — в нашем <a href="https://tproger.ru/translations/python-data-types">гайде для начинающих</a>.</p><h2>Что там по типам данных</h2><h3>List</h3><p>Начнем сравнения с list. Поскольку он изменяемый и будет интересно посмотреть, как он ведет себя при изменении количества элементов. Список представляет из себя массив не фиксированного размера, с возможностью произвольной вставки или удаления любого элемента. Элементом списка может быть любой тип данных. С точки зрения хранения списка в памяти, он состоит из двух блоков. Один блок фиксированного размера и хранит информацию о списке. Другой же хранит ссылки на элементы и может переходить из блока в блок, если количество элементов меняется.</p><p>Размер занимаемого объектом блока памяти можно посмотреть через <i>sys.getsizeof()</i>.</p><p>Как мы видим, пустой объект списка уже занимает 64 байта.</p><p>При обычном создании списка через перечисление элементов он всегда будет занимать: 64 байта пустой список + 8 байт на каждый элемент, т.к. список представляет из себя массив ссылок на объекты.</p><p>При создании через list comprehension размер будет уже 96. Что больше, чем размер пустого списка + 8 байт на каждый элемент. Работа этого механизма сводится к вызову метода append у создаваемого объекта списка. append работает следующим образом: в зависимости от уже присутствующего в списке количества элементов он заранее резервирует больше памяти при добавлении элемента. Дополнительно выделяемый объем памяти не всегда увеличивает список вдвое. Может быть выделено место всего под несколько элементов. Например, если к списку из 8-ми элементов добавляют еще один, будет зарезервировано место еще под восемь элементов. А при добавлении к списку из 16-ти элементов будет зарезервировано место всего под 9 элементов. Это позволяет избежать затрат на изменение размера списка при частых вызовах append. При этом неиспользуемая, но уже выделенная, память недоступна для чтения.</p><p>При создании списка на основе кортежа получившийся список будет занимать 112 байт. В данном случае заранее резервируется место под еще 3 элемента списка, помимо уже присутствующих в кортеже.</p><p>Итого получаем 64 байта, + 8 * 3 — это элементы из кортежа, + 8 * 3 зарезервированное место под новые элементы.</p><h3>Tuple</h3><p>Кортеж представляет из себя массив фиксированной длины, заданной при создании объекта. Элементами кортежа также могут быть объекты любых типов. В отличие от списка, кортеж в памяти представлен одним объектом. Поскольку нет изменяемой части, которую надо перемещать между блоками. Да, и методов для изменения элементов у кортежа так же нет. Но если сам элемент принадлежит к изменяемому типу, его все же можно изменить.</p><p>Пустой кортеж занимает блок из 48 байт. Помним, что кортежи неизменяемы. Если в памяти уже есть такой же кортеж, то новый объект создан не будет. Вместо этого переменной присваивается ссылка на существующий объект.</p><p>На примере видно, что адреса в памяти у двух кортежей совпадают. Аналогичное сравнение для списков вернуло бы False.</p><p>В случае непустого кортежа с памятью так же все просто. Объем блока будет равен 48 байтам + по 8 байт на каждый элемент.</p><p>С созданием кортежа из списка или другой коллекции тоже нет таких неожиданностей, как со списком. Т.к. не надо закладывать место под изменение количества элементов.</p><h3>Dict</h3><p>Словарь представляет из себя массив ключей и массив значений, где каждый ключ связан с одним значением. На ключ накладывается ограничение по уникальности в пределах словаря. Поэтому ключами могут быть объекты только неизменяемых типов. Значением же может быть объект любого типа.</p><p>Как и списки, словари хранятся в виде двух объектов. Первый, содержит информацию о самом словаре и всегда остается в одном и том же блоке. Второй, хранит пары ключ-значение и может перемещаться между блоками при изменении размера. Но при этом пустой словарь занимает гораздо больше места.</p><p>Как видно в примере, словарь занимает 240 байт. При создании словаря выделяется место под несколько элементов, а не только после добавления элемента, как это было со списком.</p><p>Если вызвать метод clear, очищаются не только все элементы. Изначально зарезервированная память тоже будет освобождена.</p><p>В итоге словарь стал занимать всего 72 байта. Гораздо меньше, чем при создании.</p><p>Как и со списком, на каждую новую пару ключ-значение весь объект не перемещается в новый блок. Чтобы избежать частых затрат на перемещение, новый блок берется с запасом на несколько элементов.</p><h2>Заключение</h2><p>Знать, какой объем памяти будет занимать тот или иной объект, полезно. Например, в условиях проекта или задачи указаны жесткие ограничения по памяти. Или кусок кода вызывается очень часто, необходимо понять, не будет ли выделение/освобождение памяти являться узким местом вашей системы.</p><p>Поэтому рекомендую, прикидывать примерные затраты памяти, еще на момент написания кода. И никто не отменяет переиспользование уже существующих объектов.</p><p>Если вы заинтересовались этой темой и решили углубиться в нее, то обратите внимание на следующие материалы:</p><p>— <a href="https://rushter.com/blog/python-memory-managment/">Memory management in Python</a>. Тут подробно описан механизм работы менеджеров памяти и организации блоков.</p><p>— <a href="https://docs.python.org/3/c-api/memory.html">Memory Management</a>. Не забываем про официальную документацию.</p><p>— <a href="http://arctrix.com/nas/python/gc/">Garbage Collection for Python</a>. Здесь детальнее рассказывают про алгоритм работы сборщика мусора.</p><p>— <a href="https://pythoninternal.wordpress.com/2014/08/04/the-garbage-collector/">The Garbage Collector</a>. И еще один материал о сборщике мусора. Базовый обзор типов — в нашем <a href="https://tproger.ru/translations/python-data-types">гайде по типам данных в Python</a>.</p>]]></content:encoded>
    </item>
    <item>
      <title>Какой язык программирования изучать в 2021 году?</title>
      <link>https://tproger.ru/articles/kakoj-jazyk-programmirovanija-izuchat-v-2021-godu</link>
      <comments>https://tproger.ru/articles/kakoj-jazyk-programmirovanija-izuchat-v-2021-godu?scrollTo=tproger-comments</comments>
      <dc:creator><![CDATA[David Medvedik]]></dc:creator>
      <guid isPermaLink="true">https://tproger.ru/articles/kakoj-jazyk-programmirovanija-izuchat-v-2021-godu</guid>
      <description><![CDATA[<p>Ведущий разработчик highload систем рассказывает о том, какой язык программирования изучать в 2021 году.</p><p>— Читать дальше «<a rel="follow" href="https://tproger.ru/articles/kakoj-jazyk-programmirovanija-izuchat-v-2021-godu">Какой язык программирования изучать в 2021 году?</a>»</p>]]></description>
      <category><![CDATA[Масштабируемость и ограничения памяти]]></category>
      <category><![CDATA[C++]]></category>
      <category><![CDATA[Пост пользователя]]></category>
      <category><![CDATA[Статьи]]></category>
      <slash:comments>0</slash:comments>
      <pubDate>Tue, 29 Jun 2021 13:07:02 GMT</pubDate>
      <content:encoded><![CDATA[<p>Меня зовут Давид Медведик, я имею большой опыт работы с высоконагруженными приложениями, разрабатывал комплексы на 200+ серверов, также являюсь одним из преподавателей курса по <a href="https://otus.ru/teacher/1833/">Highload Architect</a>. В настоящий момент работаю в одной из передовых компаний, занимающихся обработкой и анализом трафика, — Positive Technologies.</p><p>В этой статье мы поговорим о том, какой язык программирования стоит изучать в 2021 году, в чём его особенности, и причем тут highload.</p><h2>На какой язык программирования стоит обратить внимание в 2021 году</h2><p>Сейчас есть большое количество языков программирования: какие-то более универсальные, какие-то заточены под определённые задачи. Основываясь на текущих тенденциях IT-рынка, я бы рекомендовал изучать язык программирования С++.</p><h2>Почему нужно изучать С++</h2><p>Мы находимся в той точке, когда большинство компаний имеют один или несколько сервисов-проектов под высокой нагрузкой. Да, эту нагрузку можно балансировать, например, при помощи <a href="https://tproger.ru/translations/top-5-arhitekturnyh-patternov-dlja-raspredeljonnyh-sistem/">шардирования баз данных или других инструментов</a>, но язык С++ позволяет выжимать максимум из вашего железа, при этом оставаясь языком высокого уровня. Другой момент связан с тем, что из-за сложности входа в С++ большинство выбирает языки более высокого уровня абстракции, поэтому мы видим нехватку С++ программистов на рынке.</p><h2>Насколько сейчас востребован язык С++</h2><p>Достаточно взглянуть, как изменился YouTube за последние несколько лет. То количество трафика, которое он обрабатывает, растёт по экспоненте. И таких примеров много. Когда мы упираемся в «потолок» вертикального масштабирования серверов, а горизонтально масштабироваться не получается, то тут на помощь и может прийти язык С++. Так как этот язык является прямым наследником языка Си, то мы получаем большой спектр оптимизаций, при помощи которых можно выжать максимум из железа.</p><p>При этом С++ продолжает активно развиваться: добавляются механизмы и инструменты, позволяющие упростить написание кода. Если эта тенденция сохранится, то мы сможем увидеть снижение порога вхождения, что может значительно улучшить позиции языка С++ в рейтинге самых востребованных языков. И как раз в этот момент хорошо уже быть «прокачанным» специалистом.</p><h2>Какими особенностями обладает С++ по сравнению с другими языками</h2><h3>Работа с памятью в С++</h3><p>Одна из ключевых особенностей С++, которая, однако, является и минусом, и плюсом — это работа с памятью. Так как С++ является наследником языка Си, то есть возможность управлять памятью напрямую, добиваясь максимальной производительности вашей программы.</p><p>К примеру, в операционной системе Linux используется страничная адресация памяти, где каждая страница занимает около 4 KB. Когда ваше приложение использует 2 GB памяти, то вам будет выделено 2 GB/4 KB страниц. Эти страницы мапятся на физическую память.</p><p>Вся эта виртуализация требует накладных расходов, но у неё есть значительное преимущество — вашему приложению сразу же не будет выделена вся память, а только то количество, которое необходимо в данный момент. Но если ваше приложение потребляет основную часть ресурсов сервера, то вы можете выделить память не стандартного страничного размера 4 KB, а, например, целиком 2 GB, что позволит нам избавиться от накладных расходов на страничную адресацию в рамках большой страницы, она же huge pages в рамках нашего приложения.</p><p>Также стоит отметить, что C++ предоставляет нам возможность использования разного рода аллокаторов памяти при работе с объектами.</p><figure><img src="https://media.tproger.ru/uploads/2021/06/image-16.png" alt="" /></figure><p>Аллокаторы памяти — это специализированный класс, реализующий и инкапсулирующий малозначимые детали распределения и освобождения ресурсов компьютерной памяти. Они позволяют, например, избежать повторного обращения к операционной системе за новой памятью.</p><p>Но у этого механизма есть и обратная сторона: вам придётся тщательно следить за утечками памяти. Ведь кто выделил память, тот и должен её освободить. И эта проблема решена в С++. Для этого используются умные указатели — класс, предназначенный для управления динамически выделенной памятью и обеспечения её освобождения (удаления). Рекомендую самостоятельно прочитать про это, механизм достаточно интересный.</p><h3>Семантика перемещения</h3><p>Одна из самых интересных, на мой взгляд, возможностей С++ — это перемещение временных объектов в место копирования. Временные объекты  — это те объекты, которые были сконструированы во время выполнения каких-либо операций. Таким объектом может быть возвращаемое значение.</p><h3>Пропуск копии (Copy elision)</h3><p>Этот инструмент компилятора позволяет избавиться от лишних копий в рамках работы приложения. Компилятор сам анализирует код и оптимизирует его, чтобы обеспечить минимальное количество копий объектов во время работы программы.</p><p>Рассмотрим пример:</p><p>В зависимости от настроек оптимизации компилятора у нас могут быть следующие результаты:</p><p>Нам интересен последний случай. Возвращаемое значение функции f() будет сконструировано на месте объекта obj — это одна из оптимизаций, называемая return value optimization. А самое интересное, что если мы реализуем операторы перемещения, как, например, в семантике перемещения, то компилятор (с 17 стандарта) нам гарантирует, что копий не будет сделано, и объекты будут перемещены. Тем самым можно добиться результата, когда у вас в рамках приложения не создаётся ни одной копии объектов.</p><h3>Использование системных вызовов</h3><p>Одним из главных преимуществ языка С++ является возможность использования системных вызовов «как есть». Как я уже говорил, язык С++ происходит от языка Си, а именно на этом языке написано ядро Linux. Опираясь на это, мы, например, можем написать такой код в рамках С++:</p><p>Данный пример демонстрирует способ привязки ваших программных потоков к физическим ядрам системы, чтобы избежать промахов кэша процессора. Cache Miss случается, когда запрашиваемые данные отсутствуют в кэше и их нужно подгружать из основного источника.</p><p>Не знаю, можно ли так же просто в каком-либо другом языке программирования получить значительный буст производительности, просто дописав четыре строчки кода.</p><h2>Выводы</h2><p>С++ последние годы не находится в топе самых востребованных языков программирования. Оно и понятно, порой в нем используется сложный синтаксис со сложными конструкциями, тем самым порог вхождения выше, чем для более высокоуровневых языков. Тем не менее, спрос разработчиков на С++ высок, практически все крупные решения так или иначе строятся с использованием именно этого языка. При этом C++ продолжает развиваться, что в перспективе может привести к упрощению конструкций языка без потери производительности.</p><p>Я считаю, что стоит попробовать написать на этом языке хотя бы один проект ради разнообразия или чтобы глубже разобраться, как устроено взаимодействие с памятью. Поначалу он может показаться вам неприветливым, однако, заглянув чуть глубже, вы сможете разглядеть в нём гибкость и большую мощь.</p><h3>Приходилось ли вам использовать C++?</h3>]]></content:encoded>
    </item>
    <item>
      <title>Исследование утечек памяти в Go с помощью pprof</title>
      <link>https://tproger.ru/translations/memory-leaks-investigation-in-go-using-pprof</link>
      <comments>https://tproger.ru/translations/memory-leaks-investigation-in-go-using-pprof?scrollTo=tproger-comments</comments>
      <dc:creator><![CDATA[Klara Oswald]]></dc:creator>
      <guid isPermaLink="true">https://tproger.ru/translations/memory-leaks-investigation-in-go-using-pprof</guid>
      <description><![CDATA[<p>В Go нет простого способа анализировать полный дамп памяти, поэтому утечки ищут иначе. Поиск показан на примере блокчейн-инфраструктуры Orbs.</p><p>— Читать дальше «<a rel="follow" href="https://tproger.ru/translations/memory-leaks-investigation-in-go-using-pprof">Исследование утечек памяти в Go с помощью pprof</a>»</p>]]></description>
      <category><![CDATA[Масштабируемость и ограничения памяти]]></category>
      <category><![CDATA[Оптимизация]]></category>
      <category><![CDATA[Golang]]></category>
      <category><![CDATA[Переводы]]></category>
      <slash:comments>0</slash:comments>
      <pubDate>Wed, 03 Jul 2019 14:10:25 GMT</pubDate>
      <content:encoded><![CDATA[<p>Я работал с Go достаточно долго, внедряя масштабируемую блокчейн-инфраструктуру в Orbs. Мы выбрали Go из-за развитого сообщества и отличного набора инструментов.</p><p>На последних стадиях разработки могут возникнуть проблемы, связанные с производительностью, в частности утечки памяти. Мы нашли такую и в нашей системе. В этой статье я расскажу, как исследовать утечку памяти в Go, описав шаги, которые мы предприняли для её поиска.</p><p>Набор инструментов Golang имеет свои ограничения, и самое большое из них — невозможность исследовать полные дампы памяти. Полный <a href="https://ru.wikipedia.org/wiki/Дамп_памяти">дамп памяти</a> — это содержимое памяти, занятое процессом, выполняющим программу.</p><p>Отображение памяти можно представить в виде дерева. Проход по этому дереву проведёт вас через различные размещения объектов в памяти. Всё, что находится в корне дерева, удерживает память от очистки сборщиком мусора. В Go нет простого способа проанализировать дамп, поэтому сложно добраться до корней объекта, который не очищается как мусор.</p><p>На момент написания статьи в Интернете не удалось найти какой-либо способ, который помог бы найти корневой объект, препятствующий освобождению памяти. Поскольку у дампов памяти существует формат, и есть достаточно простой способ экспортировать его из отладочного пакета, вероятно, есть хотя бы один подобный инструмент, который используется в Google. Судя по тому, что можно найти в сети, для Go планируется разработать инструмент для просмотра дампов памяти, но не похоже, чтобы кто-то над этим работал.</p><h2>Утечки памяти</h2><p>Утечки памяти (или давление памяти) могут принимать разные формы. Обычно мы считаем их багами, но истинная причина возникновения может крыться ещё на стадии проектирования.</p><p>Важно построить систему таким образом, чтобы избежать преждевременных оптимизаций и позволить выполнять их позже по мере развития кода, а не перегружать его с самого начала. Некоторые распространённые примеры возникновения проблем с памятью:</p><ul><li>слишком большое количество выделений памяти, неверное представление данных;</li><li>интенсивное использование рефлексии или строк;</li><li>использование глобальных переменных;</li><li>«осиротевшие», бесконечные горутины.</li></ul><p>Самый простой способ создать утечку памяти в Go — определить глобальную переменную, массив, и добавить в него данные.</p><p>Golang предлагает инструмент с именем pprof. Он может помочь обнаружить проблемы с памятью. Он также может быть использован при обнаружении проблем в работе процессора.</p><p>pprof создаёт файл дампа, в который кладёт сэмпл кучи. Этот файл можно потом проанализировать/визуализировать, чтобы получить карту:</p><ul><li>текущего выделения памяти;</li><li>общего (накопительного) выделения памяти.</li></ul><p>У инструмента есть возможность сравнивать снимки, сделанные в разное время. Это может быть полезно при стрессовых сценариях для определения проблемных областей кода.</p><h2>Профили pprof</h2><p>pprof работает с использованием профилей.</p><p>Профиль — это набор трассировок стека, показывающих последовательности вызовов, которые привели к появлению определённого события, например к выделению памяти.</p><p>Файл <a href="https://golang.org/src/runtime/pprof/pprof.go">runtime/pprof/pprof.go</a> содержит подробную информацию и реализацию профилей.</p><p>Go имеет несколько встроенных профилей, которые можно использовать в обычных случаях:</p><ul><li>goroutine — следы всех текущих горутин;</li><li>heap — выборка выделений памяти живых объектов;</li><li>allocs — выборка всех прошлых выделений памяти;</li><li>threadcreate — следы стека, которые привели к созданию новых потоков в операционной системе;</li><li>block — следы стека, которые привели к блокировке <a href="https://docs.microsoft.com/ru-ru/dotnet/standard/threading/overview-of-synchronization-primitives">примитивов синхронизации</a>;</li><li>mutex — следы стека держателей конфликтующих <a href="https://ru.wikipedia.org/wiki/Мьютекс">мьютексов</a>.</li></ul><p>Профиль allocs идентичен heap в отношении сбора данных. Разница заключается в том, как pprof читает во время запуска. Allocs запустит pprof в режиме, который отображает общее количество байтов, выделенных с момента запуска программы (включая байты, являющиеся мусором). Нам нужно знать выделение памяти по каждому объекту отдельно, поэтому сосредоточимся на профиле heap.</p><h2>Heap</h2><p>Heap (куча) — это абстрактное представление места, где операционная система хранит объекты, которые использует код. Впоследствии эта память очищается сборщиком мусора или освобождается вручную в языках программирования без сборки мусора.</p><p>Куча — не единственное место, где происходит выделение памяти. Часть также выделяется на стеке. Его цель — быстрый доступ к памяти. В Go стек обычно используется для присвоений, которые происходят в рамках функции. Другой момент, где Go использует стек, — когда компилятор «знает», сколько памяти необходимо зарезервировать перед выполнением (например для массивов фиксированного размера).</p><p>Данные кучи должны быть освобождены с использованием сборки мусора, в то время как данные стека — нет. Поэтому гораздо эффективнее использовать стек там, где это возможно.</p><h2>Получение данных кучи с помощью pprof</h2><p>Есть два основных способа полученить данные. Первый обычно используют в качестве части теста, и он включает импорт runtime/pprof и затем вызов pprof.WriteHeapProfile(some_file) для записи информации в кучу.</p><p>WriteHeapProfile() существует для обратной совместимости. Остальные профили не имеют таких возможностей, и вы должны использовать функцию Lookup(), чтобы получить данные профилей.</p><p>Второй, более интересный способ — пустить его через HTTP (по веб-адресу). Это позволит извлекать конкретные данные из запущенного контейнера в тестовой или e2e-среде или даже из продакшна. <a href="https://golang.org/pkg/net/http/pprof/">Всю документацию пакета</a> pprof можно не читать, но как его включить, следует знать.</p><p>«Побочным эффектом» импорта net/http/pprof является регистрация конечных адресов на веб-сервере в корневом каталоге /debug/pprof. Используя curl, можно получить файлы с информацией для анализа.</p><p>Добавление http.ListenAndServe() в примере выше требуется только в случае, если ваша программа ранее не имела прослушивателя HTTP-сервера. Существуют также способы настроить его с помощью ServeMux.HandleFunc(), который понятнее для более сложной программы с поддержкой HTTP.</p><h2>Использование pprof</h2><p>Есть две основные стратегии анализа памяти с помощью pprof. Одна из них называется inuse и заключается в рассмотрении текущих выделений памяти (байтов или количества объектов). Другая носит название alloc и заключается в просматривании всех выделенных байтов или количества объектов во время выполнения программы.</p><p>Профиль heap является выборкой выделения памяти. «За кулисами» pprof использует функцию runtime.MemProfile(), которая по умолчанию собирает информацию о предоставлении памяти на каждые 512 КБ выделенных байтов. Можно изменить MemProfile() для сбора информации обо всех объектах, но скорее всего это замедлит работу приложения.</p><p>Как только файл профиля собран, пришло время загрузить его в интерактивную консоль pprof.</p><p>Посмотрим на отображаемую информацию:</p><p>Здесь важно отметить Type: inuse_space. Мы смотрим на данные выделения памяти в определённый момент (когда мы захватили профиль). Тип является значением конфигурации sample_index, а возможными значениями могут быть:</p><ul><li>inuse_space — объём выделенной и ещё не освобождённой памяти;</li><li>inuse_object s — количество выделенных и ещё не освобождённых объектов;</li><li>alloc_space — общий объём выделенной памяти (независимо от освобождённой);</li><li>alloc_objects — общее количество выделенных объектов (независимо от освобождённых).</li></ul><p>Теперь введите top в интерактивной консоли, и на выводе будут главные потребители памяти.</p><p>Можно увидеть строку, показывающую Dropped Nodes (сброшенные узлы). Узел — это выделение объекта или «узел» в дереве. Удаление узлов — хорошая идея, чтобы уменьшить количество мусора, но иногда это может скрывать основную причину утечек памяти.</p><p>Если хотите включить все данные профиля, добавьте опцию -nodefraction=0 при запуске pprof или введите nodefraction=0 в интерактивной консоли.</p><p>В выводимом списке можно увидеть два значения — flat и cum.</p><ul><li>flat означает, что память выделена функцией и удерживается ей;</li><li>cum означает, что память выделена функцией или функцией, которая была вызвана стеком.</li></ul><p>Этой информации может быть достаточно, чтобы понять, есть ли проблема. Например, функция отвечает за выделение большого объёма памяти, но не удерживает её. Это значит, что какой-то другой объект указывает на эту память и удерживает её, то есть может возникнуть утечка памяти или баг.</p><p>Команда top в интерактивной консоли по умолчанию выводит первые 10 позиций потребителей памяти. Но эта команда поддерживает формат topN, где N — количество записей, которые вы хотите увидеть. Например, при наборе top70, будут выведены все узлы.</p><h2>Графическое представление потоков выделения памяти</h2><p>Команда topN предоставляет текстовый список, но есть несколько полезных опций для визуального представления, которые есть в pprof. Можно использовать .png или .gif и многое другое (полный список можно увидеть по команде go tool pprof -help).</p><p>В нашей системе визуализация по умолчанию выглядит примерно так:</p><figure><img src="https://media.tproger.ru/uploads/2019/06/1_zX28meov6lzXVo4nCkcNvQ.jpg" alt="" /></figure><p>Это визуализация потоков выделения памяти в программе согласно трассировкам стека. Прочитать график не так сложно, как кажется. Белый квадрат с номером показывает выделенное пространство и совокупный объём памяти, который он занимает прямо сейчас. А каждый более широкий прямоугольник показывает выделившую память функцию.</p><p>Стоит отметить, что png-изображение выше было снято в режиме inuse_space. Ещё обратите внимание на inuse_objects, это также может помочь в поиске проблем с выделением.</p><h2>Копаем глубже, чтобы найти первопричину</h2><p>В нашем случае membuffers (<a href="https://github.com/orbs-network/membuffers">библиотека сериализации данных</a>) удерживает память. Но это не значит, что обязательно есть утечка в сегменте кода. Это значит, что память удерживается функцией. Важно понимать, как читать граф и вывод pprof в целом. В случае сериализации данных выделяется память для структур и примитивных объектов (int, string) и никогда не освобождается.</p><p>Неверно истолковав график, можно предположить, что один из узлов на пути к сериализации отвечает за сохранение памяти, например:</p><figure><img src="https://media.tproger.ru/uploads/2019/06/1_Yzeo5J1H0T6w6mFdgUDllQ.jpg" alt="" /></figure><p>Где-то в цепочке можно увидеть библиотеку логирования, занимающую &gt; 50 Мб выделенной памяти. Это память, которая выделяется функциями. Логгер в процессе своей работы вызывает выделение памяти, поскольку ему необходимо сериализовать данные для вывода их в журнал.</p><p>Из графа также видно, что память удерживается только сериализацией и больше ничем. Объём памяти самого логгера составляет около 30 % от общего объёма. Это значит, что проблема не в писателе. Он регистрирует что-то, чего не должно быть. А значит, утечка памяти не в журнале логов.</p><p>Знакомьтесь с командой list. Она принимает регулярное выражение, которое будет фильтровать то, что надо отобразить. Список (list) в действительности представляет собой исходный код с комментариями, относящийся к выделению. В контексте изучаемого логгера выполним list RequestNew, чтобы увидеть вызовы, сделанные в регистраторе. Эти вызовы поступают из двух функций, которые начинаются с одного и того же префикса.</p><p>Выделения памяти находятся в столбце cum. Это значит, что выделенная память сохраняется в стеке вызовов. Это соответствует тому, что показывает график. Причина выделения памяти писателем заключается в том, что мы отправили ему весь «блокирующий» объект. Нужно было как минимум сериализовать некоторые его части.</p><p>List может найти исходный код, если искать его в среде GOPATH. В случаях, когда корневой объект не совпадает (зависит от вашего сборщика), вы можете использовать опцию -trim_path. Она поможет исправить код и позволит увидеть аннотированный исходный код. Не забудьте установить свой Git на правильный коммит, который работал во время захвата профиля heap.</p><h2>Так почему память удерживается?</h2><p>В случае с Java или .Net можно использовать какой-нибудь анализатор корневых объектов и добраться до объекта, который создаёт утечку. Но с Go это невозможно из-за проблем со встроенными инструментами и из-за низкоуровневого представления памяти.</p><p>Не вдаваясь в детали, мы не думаем, что Go запоминает, какой объект по какому адресу хранится (за исключением, возможно, указателей). Для понимания, по какому адресу памяти хранится член объекта (структуры), потребуется карта вывода из профиля heap. Значит, перед захватом полного дампа ядра следует также захватить профиль heap, чтобы адреса могли быть сопоставлены с выделившими память строкой и файлом, а следовательно и объектом, представленным в памяти.</p><p>Установив значение nodefraction=0, можно увидеть всю карту выделенных объектов, включая самые маленькие.</p><figure><img src="https://media.tproger.ru/uploads/2019/06/1_VaWXnlay3BfCwKGd5ypHUg.jpg" alt="" /></figure><p>У нас есть два новых поддерева. Более длинное новое дерево зеленого цвета, которое полностью отсоединено от остальной системы, является тестовым прогоном, оно нам не интересно.</p><figure><img src="https://media.tproger.ru/uploads/2019/06/1_jj9eIWDafkAMiC_plwhoww.jpg" alt="" /></figure><p>Небольшое поддерево синего цвета, соединённое ребром со всей системой, — это inMemoryBlockPersistance. Это бэкенд, который хранит все данные в памяти, но не сохраняет их на диске. Можно сразу увидеть, что в нём находятся два больших объекта. Два — потому что размер объекта составляет 1,28 Мб, а функция сохраняет 2,57 Мб, то есть в два раза больше.</p><p>Теперь проблема отчётливо ясна. Можно использовать отладчик и увидеть, что всему виной массив, который удерживает все блоки для постоянства памяти.</p><h2>Что можно исправить?</h2><p>Десериализованные данные занимали слишком много памяти. Откуда взялось 142 Мб для чего-то, что должно занимать существенно меньше памяти? Здесь может помочь pprof — он существует именно для того, чтобы точно отвечать на такие вопросы.</p><p>Чтобы просмотреть исходный код функции, запустим lazyCalcOffsets(). Теперь можем увидеть, что flat и cum одинаковы. Это указывает на то, что выделенная память также сохраняется этими точками выделения. Функция make() тоже занимает немного памяти, т. к. это указатель на структуру данных. Ещё можно увидеть, что занимает память выделение в строке 43.</p><p>Следует отметить, что функция make() в Go позволяет делать не только отображения, но и <a href="https://sefus.ru/little-go-book-3/">срезы</a>. Присвоение отображению не то же самое, что присвоение простой переменной — использование переменных предполагает большие издержки ресурсов. Чем больше элементов, тем больше будут эти издержки по сравнению со срезом.</p><p>При использовании map[int]T когда данные не редки или могут быть преобразованы в последовательные индексы и потребление памяти является важным фактором, для большей эффективности следует использовать срез. Однако большой срез при расширении данных может замедлить операцию, в то время как для отображения это замедление будет незначительным. Как вы уже поняли, не существует волшебной формулы для оптимизации.</p><p>В нашей системе мы теперь используем срез вместо отображения. Из-за способа получения данных и того, как мы получаем к ним доступ, кроме пары строк и структуры данных, никаких других изменений кода не потребовалось. Посмотрим, как это повлияло на потребление памяти.</p><p>Взглянем на benchcmp для пары тестов.</p><p>Тесты на чтение инициализируют структуру данных, которая создаёт выделения. Время выполнения улучшилось на ~30 %, выделение сократилось на 50 %, а потребление памяти &gt; 90 %.</p><p>Поскольку отображение (теперь уже срез) никогда не было заполнено большим количеством элементов, цифры показывают то, что мы увидим в продакшене. Результат зависит от энтропии данных, но могут быть случаи, когда улучшение как выделения, так и потребления памяти было бы ещё больше.</p><p>Ещё раз задействовав pprof и захватив профиль heap из того же теста, можно увидеть, что теперь потребление памяти фактически сократилось на ~90 %.</p><figure><img src="https://media.tproger.ru/uploads/2019/06/1_d8jhBw3h_ZrHvbgv95Pjvw.jpg" alt="" /></figure><p>Вывод прост — для небольших наборов данных не следует использовать отображения, достаточно срезов. Отображения имеют большие издержки.</p><h2>Полный дамп памяти</h2><p>Именно здесь можно наблюдать самое большое ограничение инструментов Go. Этот язык развивается большими темпами, но это развитие имеет свою цену в случае полного дампа и выделения памяти. Формат полного дампа кучи по мере своих обновлений является обратно не совместимым. Помните это, когда будете использовать <a href="https://github.com/golang/go/wiki/heapdump15-through-heapdump17">последнюю версию</a>. Для записи полного дампа кучи вы можете использовать debug.WriteHeapDump().</p><p>Следует также отметить, что не существует хорошего решения для изучения полных дампов. Вот некоторые вещи, которые стоит игнорировать, если собираетесь попробовать открыть полный дамп самостоятельно, начиная с Go 1.11:</p><ul><li>В macOS нет способа открыть и отладить полный дамп ядра, это работает только в Linux.</li><li>Инструменты в <a href="https://github.com/randall77/hprof">этом репозитории</a> предназначены для Go 1.3. Существует форк для 1.7+, но он также не работает должным образом (не полностью).</li><li><a href="https://github.com/golang/debug/tree/master/cmd/viewcore">ViewCore из репозитория Go</a> на самом деле не компилируется. Это достаточно легко исправить, указав внутренние пакеты на golang.org вместо github.com. Но это также не работает на macOS и может быть работает на Linux.</li><li>Также <a href="https://github.com/randall77/corelib">corelib</a> не работает на macOS.</li></ul><p>Ещё одна важная деталь о pprof — это его интерфейс. Он может сэкономить много времени при исследовании проблем профилей, созданных с помощью pprof.</p>]]></content:encoded>
    </item>
  </channel>
</rss>