惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

L
LINUX DO - 最新话题
C
Cyber Attacks, Cyber Crime and Cyber Security
G
GRAHAM CLULEY
T
Tenable Blog
T
Threatpost
C
CXSECURITY Database RSS Feed - CXSecurity.com
I
Intezer
cs.CL updates on arXiv.org
cs.CL updates on arXiv.org
D
Darknet – Hacking Tools, Hacker News & Cyber Security
K
Kaspersky official blog
Security Latest
Security Latest
P
Privacy & Cybersecurity Law Blog
Google Online Security Blog
Google Online Security Blog
SecWiki News
SecWiki News
P
Palo Alto Networks Blog
TaoSecurity Blog
TaoSecurity Blog
Webroot Blog
Webroot Blog
Spread Privacy
Spread Privacy
O
OpenAI News
The Last Watchdog
The Last Watchdog
P
Proofpoint News Feed
C
Check Point Blog
cs.CV updates on arXiv.org
cs.CV updates on arXiv.org
人人都是产品经理
人人都是产品经理
S
Security @ Cisco Blogs
Scott Helme
Scott Helme
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
cs.AI updates on arXiv.org
cs.AI updates on arXiv.org
月光博客
月光博客
S
Securelist
酷 壳 – CoolShell
酷 壳 – CoolShell
V
V2EX
T
Troy Hunt's Blog
W
WeLiveSecurity
GbyAI
GbyAI
N
News | PayPal Newsroom
Y
Y Combinator Blog
C
Cisco Blogs
H
Help Net Security
The GitHub Blog
The GitHub Blog
Threat Intelligence Blog | Flashpoint
Threat Intelligence Blog | Flashpoint
博客园 - 【当耐特】
Jina AI
Jina AI
MongoDB | Blog
MongoDB | Blog
P
Proofpoint News Feed
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
CTFtime.org: upcoming CTF events
CTFtime.org: upcoming CTF events
云风的 BLOG
云风的 BLOG
小众软件
小众软件
N
News and Events Feed by Topic

Все публикации подряд на Хабре

Ловим музу за клавиатуру: как айтишнику стать автором Что умеет Midjourney в 2026? Мой немного грустный разбор этого шикарного инструмента Никто не любит писать тесты, но ИИ может исправить это IPv8 выглядит как мечта. Поэтому почти наверняка не взлетит Производители вернули в продажу материнки с DDR3. Что происходит? Управление агентом с телефона через Telegram теперь в KodaCode От координации к лидерству: как меняется роль руководителя разработки Я сделала родителям бизнес вместо пенсии: зарабатываем 70 тысяч, мама не даёт продать В три раза быстрее приемка товара и оптимизация трудозатрат на 73%: как «РСТ-Инвент» помог Gulliver Group ИИ-шечный мир победил? О влиянии искусственного интеллекта на игропром Кремль снижает давление на Телеграмм пока Европа строит интернет по паспорту Как CEO, CTO и CIO за 8 часов собрали ИИ-директора, который умеет держать позицию под давлением Как (не) потерять домен за выходные Вместо 8 разных VPS: как я организовал практику студентам на одном сервере Почему твой Open Source проект не замечают? R&D: искусство управления неопределенностью в разработке AI-дефляция: вакансий для разработчиков больше, а рост зарплат — худший за 15 лет Мы отдали управление роботами OpenClaw. Что из этого вышло Галактический ID: система идентификации для всех форм разумной жизни Шесть основ бизнес-анализа: начинаем с вопроса «Кто в игре?» Код-ревью, в котором дело не в коде Данные переехали. Команда — нет Системной подход к сдаче OSWE в 2025 Почему комната управления реактором покрашена в цвет морской пены 4 YAML-файла вместо PySpark: как аналитикам строить пайплайны без разработчиков LLM-агент для поиска свободных доменов: автоматизируем подбор Когда, зачем и как правильно начинать новую сессию в Claude Code? Как я заставил нейросеть писать макросы для FreeCAD Анатомия ИИ‑агента для подбора персонала. От тысячи резюме к топ‑10 за минуты Опыт разработчика как экономика внимания Автономность как точка невозврата: кто будет субъектом в цифровом будущем Обучение ИИ в «диких» условиях: как рутинные действия превращаются в датасеты Как измерить LLM для задач кибербеза: обзор открытых бенчмарков Где хранить код? Сравнение GitHub, GitLab и Bitbucket Математика объясняет, почему нормальное распределение встречается повсюду Почему ваш FinOps не работает: 12 тезисов от практиков Как подписать проектную документацию УКЭП с использованием бесплатных лицензий Pilot Адаптивное администрирование Sigla Vision Я грузил уран в бочки, а потом 20 лет строил ИТ в атомной отрасли Чем позвонить с Эвереста? История и обзор спутниковой связи. Часть 2 Как языковая модель помогает контролировать качество инструктажей по охране труда в металлургии Как не передать на desktop свой IP в РКН Анатомия SAP Privileges: как устроено управление правами в macOS MoneyDev: Сказка про три главных слова Обновлённый токенизатор видео K-VAE 2.0 от Сбера Как сделать диспетчеризацию дома на 1284 квартиры почти бесплатно Как мы разогнали железную дорогу Мы дали агентам рутину. Теперь надо решить — что делать с освободившимся временем Токсичный контент, промпт-хакинг и защита ИИ — всё о Guardrails для LLM Умный город начинается с точного взгляда: как «Фалькон Тех» меняет пространство к лучшему Навайбкодил приложение для анализа графов Почему Дюну так интересно читать? Упрощаем работу с рутиной или как стать Гендальфом Белым Деконструкция Go: CPU, RAM и что там происходит. Go Assembler база. Часть 1.1 Какие профессии исчезнут из-за ИИ, а какие появятся? И что с этим делать Как мы построили IT-отдел, где хочется расти: архитектурные встречи, прозрачные метрики и книжные подарки Rufler: Делаем из Claude Code автономный рой через один YAML-конфиг Sing-box и белый список приложений Как построить надёжный обмен сообщениями в микросервисах: лучшие практики для enterprise OpenAI строит MLM-пирамиду, а McKinsey и Accenture помогают ей в этом Дом, который не построил Фишер (Часть 2) «Сверхзвуковой математик» против «Вдумчивого логиста»: битва алгоритмов 3D-упаковки Мультимодальные модели – грубый и дорогой инструмент Разговоры ничего не стоят. Код тоже Проверки физических лиц: с кого начнет ФНС Топ-10 бесплатных нейросетей для создания видео в 2026 году Первые слои кода: как наши решения сегодня определяют архитектуру ИИ на десятилетия Разработка нового статического анализатора: PVS-Studio JavaScript Поиск уязвимостей ПО: базовый минимум или роскошный максимум Почему оценка персонала не работает как инструмент управления Как мы разработали ИИ-ассистента и сократили рутину продуктовой команды на 50% Как я ушел из найма, нажарил косточек и продал на маркетплейсах на 168 млн в год Когда 1С:ERP уже внедрена, а нормального производственного плана всё ещё нет Как я сделал Claude мультимодальным, подключив к нему Qwen Omni Как приглашение на вакансию мечты превращается в атаку Infrastructure as Code: философия и лучшие практики IaC Тестируем Yandex Code Assistant на задаче, в которой нужно хранить секреты nxs-universal-chart v3.0: новое поколение универсального Helm-чарта Callback Injection: Техника, которая отправила Microsoft Defender в глухой нокаут «Все идеи на стол»: митап как способ вывести проект из тупика Сегодня я узнал нечто новое о GPU благодаря багу в своей игре Как заставить LLM ̶ ̶г̶а̶л̶л̶ю̶ ̶ эволюционировать Карта событий как фундамент аналитики: практический кейс для E-commerce Что выбрать для AI: x86, ARM или RISC-V? Дайджест железа за март Роль соматических мутаций в развитии аутоиммунных заболеваний: путь к избирательной терапии Mythos от Anthropic — тревожный сигнал для всех, а не только для банков Guardrails для LLM на Java: как приручить промпт‑инъекции и токсичные ответы Green-VLA: как мы собрали VLA-модель для реального антропоморфного робота и не потеряли обобщение Финансовая гонка вооружений: почему умные люди добровольно в ней участвуют Эра ИИ-агентов наступила: выбираем лучшего цифрового сотрудника # Практический опыт внедрения WinCC Redundancy на производственном предприятии Сделал MVP за 3 дня, а потом неделю прикручивал оплату. Оно того стоило? Физика против Маска: почему Starship V3 может оказаться ещё одной катастрофой Нефть Венесуэлы: крупнейшие запасы в мире, но не крупнейшая нефтяная держава JPA 4. Переосмысление Hibernate Почему зеркальная фотокамера Nikon D5 десятилетней давности идеально подошла для миссии «Артемида-2» Проект «Уровень-Спутник» или как мы сделали платформу для гидрологов «Замедлиться, чтобы ускориться»: почему ИИ повышает цену ошибок в требованиях и архитектуре Как с нуля поднять трафик IT-компании на 1657% при бюджете 55 тыс. и выжить Pixel-perfect Downsampling — идеальная отрисовка 50 миллионов точек без потерь
Трассируем чтение 8 КБ из PostgreSQL
PatientZero · 2026-06-19 · via Все публикации подряд на Хабре

Простой

6 мин

0

Какое-то время назад у меня возник инцидент с IOPS в продакшене (я уже писал о нём). Однако у меня не было никакой возможности замерить происходившее. Так как EBS скрывает от меня все механизмы, я решил замерить поведение того запроса в контролируемой мной среде. План такой: я выполняю один и тот же запрос трижды, каждый раз замеряя показания (сначала со страницами в общих буферах, затем со страницами, которые находятся только в кэше страниц операционной системы и, наконец, при чтении всего с диска). После этого я сравню результаты с двумя дисками, скрытыми под облачными абстракциями: с томом EBS из инцидента и с сервером Hetzner, бенчмарк которого я уже проводил.

Система довольно проста: моя домашняя машина с Debian. У меня работает Postgres 17 в Docker с shared_buffers = 16MB, track_io_timing = on. В качестве накопителя используется локальный SSD NVMe с ext4. Я намеренно создал таблицу такого размера, чтобы она не умещалась в кэш:

postgres=# SELECT pg_size_pretty(pg_relation_size('leads')), pg_relation_size('leads')/8192;
 pg_size_pretty | ?column?
----------------+----------
 115 MB         |    14706

Смысл создания таблицы размером гораздо больше shared_buffers заключается в том, чтобы гарантировать, что полное чтение не уместится в собственный кэш.

Таблицу я смоделировал на основе той, которая поломалась в продакшене: в ней есть id, account_id и жирная нагрузка в jsonb. Также есть только обычный индекс в B-дереве для account_id. Два наихудших запроса в инциденте выполняли одно и то же действие: производили фильтрацию по account_id с индексом, а затем применяли второй фильтр для неиндексированного столбца после получения строк. Именно это я и воссоздаю. Для аккаунта 42:

postgres=# SELECT count(*), count(DISTINCT (ctid::text::point)[0]) AS heap_pages
postgres-#   FROM leads WHERE account_id = 42;
 count | heap_pages
-------+------------
   500 |        500

Итак, у нас есть 500 строк, разбросанных по 500 отдельным страницам кучи. Для чтения аккаунта 42 Postgres читает индекс, чтобы узнать местоположение строк, а затем получает из кучи 500 отдельных страни. Выполняемый мной запрос добавляет фильтр по payload, который индекс не может рассчитать, поэтому загружаются и отбрасываются все 500 строк.

Index Scan using idx_account_id on leads
  Index Cond: (account_id = 42)
  Filter: (payload ~~ '%zzzz%'::text)
  Rows Removed by Filter: 500

Да, мы получили 4 МБ совершенно бесполезного ввода-вывода (см. строку Rows Removed by Filter). Теперь в этой системе мы выполним один и тот же запрос в трёх состояниях кэша, каждый раз считывая строку Buffers и наблюдая за происходящим.

Общие буферы

Давайте выполним запрос во второй раз, пока страницы всё ещё тёплые:

Index Scan using idx_account_id on leads (actual time=0.534..0.534 rows=0)
  Buffers: shared hit=504
Execution Time: 0.569 ms

shared hit=504 — каждая страница уже находится в памяти процесса, поэтому никаких системных вызовов нет. pg_buffercache подтверждает, что они резидентные:

postgres=# SELECT c.relname, count(*) FROM pg_buffercache b
postgres-#   JOIN pg_class c ON b.relfilenode = pg_relation_filenode(c.oid)
postgres-#   WHERE c.relname IN ('leads','idx_account_id') GROUP BY 1;
    relname     | count
----------------+-------
 leads          |   500
 idx_account_id |     5

0,57 мс. Хороший показатель.

Кэш страниц операционной системы

Теперь давайте удалим страницы из общих буферов — перезапуск стирает их, потому что общие буферы находятся в памяти процесса. По очевидным причинам мы не будем удалять кэш страниц операционной системы. При холодных общих буферах и тёплом кэше операционной системы результаты оказываются такими:

Index Scan using idx_account_id on leads (actual time=2.292..2.293 rows=0)
  Buffers: shared read=504
  I/O Timings: shared read=2.024
Execution Time: 2.316 ms

Вместо shared hit, который мы получили при первой попытке, теперь у нас есть shared read, то есть нам нужно отправить системный вызов read(2). Postgres не знает поведения системного вызова. С точки зрения Postgres, произошло 504 промаха: 504 вызова read(2). Но взгляните на тайминги — 2024 мс для всех 504 операций чтения, то есть примерно по 4 микросекунды на каждую.

Мы можем считать, что диск не задействовался, потому что показатели задержек хорошо известны. Но можно и сравнить эти значения с показателями следующего прогона.

Диск

Теперь мы очистим кэш страниц операционной системы и выполнять чтение можно будет только с SSD (разумеется, данные могут находиться в DRAM SSD, но я знаю, что у моей локальной модели нет DRAM). Для этого нужны два этапа: перезапустить Postgres для очистки её собственной памяти, а затем echo 3 > /proc/sys/vm/drop_caches для сброса кэша страниц операционной системы, после чего снова выполнить тот же запрос.

Index Scan using idx_account_id on leads (actual time=39.991..39.991 rows=0)
  Buffers: shared read=504
  I/O Timings: shared read=39.502
Execution Time: 40.276 ms

39,5 ms — примерно по 78 микросекунд на чтение, в двадцать раз медленнее, чем кэш страниц.

Давайте сравним этот показатель и baremetal-устройство при помощи fio и произвольного чтения 8 КБ при помощи --direct=1 для обхода кэша страниц.

frn@debian:~$ fio --name=randread --filename=/home/frn/.fio_test --size=1G \
              --bs=8k --rw=randread --direct=1 --ioengine=psync --iodepth=1 \
              --runtime=8 --time_based
...
read: IOPS=13.2k
clat (usec): min=61, avg=75.70, 99.00th=[84]

76 микросекунд на чтение; Postgres замерила 78, так что они согласуются. Это подтверждает, что затраты связаны с самим SSD.

Кроме того, я поэкспериментировал и с чтением тех же самых 500 холодных страниц в отсортированном порядке вместо порядка по индексу. Затраты оказались равны 2,8 мс. Это меня сильно удивило, однако ядро замечает последовательный паттерн и загружает следующие страницы в кэш страниц ещё до того, как их запросит Postgres. Если страницы поступают в произвольном порядке, ядро не может предугадать, какая будет следующей, поэтому упреждающее чтение ничего не делает и каждая операция чтения ждёт NVMe.

Но ниже есть и ещё один слой. В продакшене диск был не локальным SSD, а подключенным по сети AWS EBS. План, обрушивший наши IOPS, был таким:

Index Scan using idx_account_id on leads
  Filter: ((some_column IS NULL) AND (jsonb_condition))
  Rows Removed by Filter: 39811
  Buffers: shared hit=10871 read=27841 dirtied=3
  I/O Timings: shared/local read=13838.335

27841 операция чтения: их операционная система не может обслужить из кэша. 13838 мс времени на ввод-вывода, примерно по 497 микросекунд на каждую операцию чтения. Если сравнить, затраты на считывание одной и той же страницы на 8 КБ чётко снижаются. Страница в общих буферах стоит одну операцию чтения из памяти. Затраты на страницу из кэша страниц ядра повышается примерно до 4 микросекунд. Холодные считывания с локальных NVMe стоят 78 микросекунд. А в продакшене при получении из EBS по сети — примерно 497 микросекунд. Каждый шаг вниз стоит примерно одного порядка величин: EBS где-то в 6 раз медленнее локального SSD и в 124 раз медленнее, чем кэш страниц (по крайней мере, в примере).


Ещё один облачный диск

Также мне хотелось проверить, проблема ли это EBS или она связана с сетью. В процессе написания этого поста я вспомнил, что пару месяцев назад выбирал сервер для кластера Postgres — Hetzner CCX33: 8 vCPU, 32 ГБ ОЗУ, 240 ГБ NVMe; на нём работал fio. Стоит сравнить его показатели с показателями EB.

Сначала произвольное чтение по 8 КБ с бОльшим давлением, чем при домашнем тесте (--iodepth=32 --numjobs=4 и тот же --direct=1):

read: IOPS=325k
clat: p1=668ns, p50=123us, p99=1.3ms

325 тысяч IOPS и p50 в 123 микросекунд; теоретически, быстрее, чем домашный NVMe. Однако p1 равен 668 наносекундам, а NVMe дома никогда не отвечал быстрее, чем за 61 микросекунду: это время, которое требуется самой флэш-памяти. Скорости меньше микросекунды — это ОЗУ. --direct=1 минует кэш страниц VM, но на облачном сервере между VM и накопителем есть гипервизор, у которого имеется собственный кэш. Часть из этих операций чтения не выходит за пределы ОЗУ хоста, поэтому тест измеряет и кэши, и диск.

Операции записи с fsync не имеют этой проблемы, потому что fsync выполняет возврат только тогда, когда данные сохранены на постоянный накопитель:

root@ubuntu-32gb-hil-1:~$ fio --name=randwrite-fsync --rw=randwrite --bs=8k --fsync=1 \
              --iodepth=32 --numjobs=4 --runtime=120 --ioengine=libaio \
              --direct=1 --group_reporting --time_based --ramp_time=10 \
              --size=20G --filename=/mnt/bench/test.bin
...
write: IOPS=10.2k
clat: p50=12.1ms, p99=16.2ms

12 миллисекунд на то, чтобы 8 КБ записались на накопитель. Чтение — это не запись, так что сравнение с числами выше неточное, но смысл здесь в масштабе. Домашний NVMe отвечает за десятки микросекунд. Чтение в EBS занимает примерно 500. Здесь запись на накопитель занимает 12 тысяч. Что бы ни находилось под этой VM, оно ведёт себя как том EBS, а не как локальный NVMe.


Тонкости

Теперь я почти доволен, но полного ответа на вопрос нет. Показатель EBS в сравнении с показателем моего локального NVMe — это лишь наиболее точная догадка. Значения взяты из инцидента в продакшене, поэтому среда была неодинаковой. На них влияет многое: ввод-вывод, очистка общих буферов, другие запросы, соединения и так далее.

Показатели Hetzner — это тоже аппроксимация. Я использовал для них разные ioengine и глубины. При бенчмаркинге машины Hetzner я пытался проверить, действительно ли это локальный диск, поэтому протестировал общую дисковую производительность при помощи libaio и с iodepth=32. Для сравнения: при бенчмаркинге локального NVMe использовался psync — он синхронный и блокирующий, тут всё просто.

Однако, несмотря на всё это, я доволен, потому что из всего этого удалось извлечь общий принцип. Мы провели глубокий анализ и получили нечто полезное, пусть и детали довольно неточные.