Π­ΠΊΠΎΠ½ΠΎΠΌΠΈΠΌ ΠΊΠΎΠΏΠ΅Π΅Ρ‡ΠΊΡƒ Π½Π° Π±ΠΎΠ»ΡŒΡˆΠΈΡ… ΠΎΠ±ΡŠΠ΅ΠΌΠ°Ρ… Π² PostgreSQL

ΠŸΡ€ΠΎΠ΄ΠΎΠ»ΠΆΠ°Ρ Ρ‚Π΅ΠΌΡƒ записи Π±ΠΎΠ»ΡŒΡˆΠΈΡ… ΠΏΠΎΡ‚ΠΎΠΊΠΎΠ² Π΄Π°Π½Π½Ρ‹Ρ…, ΠΏΠΎΠ΄Π½ΡΡ‚ΡƒΡŽ ΠΏΡ€Π΅Π΄Ρ‹Π΄ΡƒΡ‰Π΅ΠΉ ΡΡ‚Π°Ρ‚ΡŒΠ΅ΠΉ ΠΏΡ€ΠΎ сСкционированиС, Π² этой рассмотрим способы, ΠΊΠΎΡ‚ΠΎΡ€Ρ‹ΠΌΠΈ ΠΌΠΎΠΆΠ½ΠΎ ΡƒΠΌΠ΅Π½ΡŒΡˆΠΈΡ‚ΡŒ «физичСский» Ρ€Π°Π·ΠΌΠ΅Ρ€ Ρ…Ρ€Π°Π½ΠΈΠΌΠΎΠ³ΠΎ Π² PostgreSQL, ΠΈ ΠΈΡ… влияниС Π½Π° ΠΏΡ€ΠΎΠΈΠ·Π²ΠΎΠ΄ΠΈΡ‚Π΅Π»ΡŒΠ½ΠΎΡΡ‚ΡŒ сСрвСра.

Π Π΅Ρ‡ΡŒ ΠΏΠΎΠΉΠ΄Π΅Ρ‚ ΠΏΡ€ΠΎ настройки TOAST ΠΈ Π²Ρ‹Ρ€Π°Π²Π½ΠΈΠ²Π°Π½ΠΈΠ΅ Π΄Π°Π½Π½Ρ‹Ρ…. Β«Π’ срСднСм» эти способы позволят ΡΡΠΊΠΎΠ½ΠΎΠΌΠΈΡ‚ΡŒ Π½Π΅ слишком ΠΌΠ½ΠΎΠ³ΠΎ рСсурсов, Π·Π°Ρ‚ΠΎ β€” Π²ΠΎΠΎΠ±Ρ‰Π΅ Π±Π΅Π· ΠΌΠΎΠ΄ΠΈΡ„ΠΈΠΊΠ°Ρ†ΠΈΠΈ ΠΊΠΎΠ΄Π° прилоТСния.

Π­ΠΊΠΎΠ½ΠΎΠΌΠΈΠΌ ΠΊΠΎΠΏΠ΅Π΅Ρ‡ΠΊΡƒ Π½Π° Π±ΠΎΠ»ΡŒΡˆΠΈΡ… ΠΎΠ±ΡŠΠ΅ΠΌΠ°Ρ… Π² PostgreSQL
Однако, наш ΠΎΠΏΡ‹Ρ‚ оказался вСсьма ΠΏΡ€ΠΎΠ΄ΡƒΠΊΡ‚ΠΈΠ²Π½Ρ‹ΠΌ Π² этом ΠΏΠ»Π°Π½Π΅, ΠΏΠΎΡΠΊΠΎΠ»ΡŒΠΊΡƒ Ρ…Ρ€Π°Π½ΠΈΠ»ΠΈΡ‰Π΅ ΠΏΠΎΡ‡Ρ‚ΠΈ любого ΠΌΠΎΠ½ΠΈΡ‚ΠΎΡ€ΠΈΠ½Π³Π° ΠΏΠΎ своСй ΠΏΡ€ΠΈΡ€ΠΎΠ΄Π΅ являСтся большСй Ρ‡Π°ΡΡ‚ΡŒΡŽ append-only с Ρ‚ΠΎΡ‡ΠΊΠΈ зрСния записываСмых Π΄Π°Π½Π½Ρ‹Ρ…. И Ссли Π²Π°ΠΌ интСрСсно, ΠΊΠ°ΠΊ ΠΌΠΎΠΆΠ½ΠΎ Π½Π°ΡƒΡ‡ΠΈΡ‚ΡŒ Π±Π°Π·Ρƒ ΠΏΠΈΡΠ°Ρ‚ΡŒ Π½Π° диск вмСсто 200MB/s Π²Π΄Π²ΠΎΠ΅ мСньшС β€” ΠΏΡ€ΠΎΡˆΡƒ ΠΏΠΎΠ΄ ΠΊΠ°Ρ‚.

МалСнькиС сСкрСты Π±ΠΎΠ»ΡŒΡˆΠΈΡ… Π΄Π°Π½Π½Ρ‹Ρ…

По ΠΏΡ€ΠΎΡ„ΠΈΠ»ΡŽ Ρ€Π°Π±ΠΎΡ‚Ρ‹ нашСго сСрвиса, Π΅ΠΌΡƒ рСгулярно ΠΏΡ€ΠΈΠ»Π΅Ρ‚Π°ΡŽΡ‚ ΠΈΠ· Π»ΠΎΠ³ΠΎΠ² тСкстовыС ΠΏΠ°ΠΊΠ΅Ρ‚Ρ‹.

А ΠΏΠΎΡΠΊΠΎΠ»ΡŒΠΊΡƒ комплСкс Π‘Π‘Π˜Π‘, Ρ‡ΡŒΠΈ Π‘Π” ΠΌΡ‹ ΠΌΠΎΠ½ΠΈΡ‚ΠΎΡ€ΠΈΠΌ, β€” это ΠΌΠ½ΠΎΠ³ΠΎΠΊΠΎΠΌΠΏΠΎΠ½Π΅Π½Ρ‚Π½Ρ‹ΠΉ ΠΏΡ€ΠΎΠ΄ΡƒΠΊΡ‚ со слоТными структурами Π΄Π°Π½Π½Ρ‹Ρ…, Ρ‚ΠΎ ΠΈ запросы для достиТСния максимальной ΠΏΡ€ΠΎΠΈΠ·Π²ΠΎΠ΄ΠΈΡ‚Π΅Π»ΡŒΠ½ΠΎΡΡ‚ΠΈ ΠΏΠΎΠ»ΡƒΡ‡Π°ΡŽΡ‚ΡΡ Π²ΠΏΠΎΠ»Π½Π΅ Ρ‚Π°ΠΊΠΈΠΌΠΈ Β«ΠΌΠ½ΠΎΠ³ΠΎΡ‚ΠΎΠΌΠ½ΠΈΠΊΠ°ΠΌΠΈΒ» со слоТной алгоритмичСской Π»ΠΎΠ³ΠΈΠΊΠΎΠΉ. Π’Π°ΠΊ Ρ‡Ρ‚ΠΎ ΠΈ объСм ΠΊΠ°ΠΆΠ΄ΠΎΠ³ΠΎ ΠΎΡ‚Π΄Π΅Π»ΡŒΠ½ΠΎΠ³ΠΎ экзСмпляра запроса ΠΈΠ»ΠΈ Ρ€Π΅Π·ΡƒΠ»ΡŒΡ‚ΠΈΡ€ΡƒΡŽΡ‰Π΅Π³ΠΎ ΠΏΠ»Π°Π½Π° выполнСния Π² ΠΏΠΎΡΡ‚ΡƒΠΏΠ°ΡŽΡ‰Π΅ΠΌ ΠΊ Π½Π°ΠΌ Π»ΠΎΠ³Π΅ оказываСтся Β«Π² срСднСм» достаточно большим.

Π”Π°Π²Π°ΠΉΡ‚Π΅ посмотрим Π½Π° структуру ΠΎΠ΄Π½ΠΎΠΉ ΠΈΠ· Ρ‚Π°Π±Π»ΠΈΡ†, Π² ΠΊΠΎΡ‚ΠΎΡ€ΡƒΡŽ ΠΌΡ‹ пишСм «сырыС» Π΄Π°Π½Π½Ρ‹Π΅ β€” Ρ‚ΠΎ Π΅ΡΡ‚ΡŒ Π²ΠΎΡ‚ прямо ΠΎΡ€ΠΈΠ³ΠΈΠ½Π°Π»ΡŒΠ½Ρ‹ΠΉ тСкст ΠΈΠ· записи Π»ΠΎΠ³Π°:

CREATE TABLE rawdata_orig(
  pack -- PK
    uuid NOT NULL
, recno -- PK
    smallint NOT NULL
, dt -- ΠΊΠ»ΡŽΡ‡ сСкции
    date
, data -- самоС главноС
    text
, PRIMARY KEY(pack, recno)
);

Випичная такая Ρ‚Π°Π±Π»ΠΈΡ‡ΠΊΠ° (ΡƒΠΆΠ΅ сСкционированная, бСзусловно, поэтому это β€” шаблон сСкции), Π³Π΄Π΅ самоС Π²Π°ΠΆΠ½ΠΎΠ΅ β€” тСкст. Иногда достаточно ΠΎΠ±ΡŠΠ΅ΠΌΠ½Ρ‹ΠΉ.

Вспомним, Ρ‡Ρ‚ΠΎ «физичСский» Ρ€Π°Π·ΠΌΠ΅Ρ€ ΠΎΠ΄Π½ΠΎΠΉ записи Π² PG Π½Π΅ ΠΌΠΎΠΆΠ΅Ρ‚ Π·Π°Π½ΠΈΠΌΠ°Ρ‚ΡŒ большС ΠΎΠ΄Π½ΠΎΠΉ страницы Π΄Π°Π½Π½Ρ‹Ρ…, Π½ΠΎ «логичСский» Ρ€Π°Π·ΠΌΠ΅Ρ€ β€” совсСм Π΄Ρ€ΡƒΠ³ΠΎΠ΅ Π΄Π΅Π»ΠΎ. Π§Ρ‚ΠΎΠ±Ρ‹ Π·Π°ΠΏΠΈΡΠ°Ρ‚ΡŒ Π² ΠΏΠΎΠ»Π΅ объСмноС Π·Π½Π°Ρ‡Π΅Π½ΠΈΠ΅ (varchar/text/bytea) ΠΈΡΠΏΠΎΠ»ΡŒΠ·ΡƒΠ΅Ρ‚ΡΡ тСхнология TOAST:

PostgreSQL ΠΈΡΠΏΠΎΠ»ΡŒΠ·ΡƒΠ΅Ρ‚ фиксированный Ρ€Π°Π·ΠΌΠ΅Ρ€ страницы (ΠΎΠ±Ρ‹Ρ‡Π½ΠΎ 8 ΠšΠ‘), ΠΈ Π½Π΅ позволяСт ΠΊΠΎΡ€Ρ‚Π΅ΠΆΠ°ΠΌ Π·Π°Π½ΠΈΠΌΠ°Ρ‚ΡŒ нСсколько страниц. ΠŸΠΎΡΡ‚ΠΎΠΌΡƒ нСпосрСдствСнно Ρ…Ρ€Π°Π½ΠΈΡ‚ΡŒ ΠΎΡ‡Π΅Π½ΡŒ большиС значСния ΠΏΠΎΠ»Π΅ΠΉ Π½Π΅Π²ΠΎΠ·ΠΌΠΎΠΆΠ½ΠΎ. Для прСодолСния этого ограничСния большиС значСния ΠΏΠΎΠ»Π΅ΠΉ ΡΠΆΠΈΠΌΠ°ΡŽΡ‚ΡΡ ΠΈ/ΠΈΠ»ΠΈ Ρ€Π°Π·Π±ΠΈΠ²Π°ΡŽΡ‚ΡΡ Π½Π° нСсколько физичСских строк. Π­Ρ‚ΠΎ происходит Π½Π΅Π·Π°ΠΌΠ΅Ρ‚Π½ΠΎ для ΠΏΠΎΠ»ΡŒΠ·ΠΎΠ²Π°Ρ‚Π΅Π»Ρ ΠΈ Π½Π° Π±ΠΎΠ»ΡŒΡˆΡƒΡŽ Ρ‡Π°ΡΡ‚ΡŒ ΠΊΠΎΠ΄Π° сСрвСра влияСт Π½Π΅Π·Π½Π°Ρ‡ΠΈΡ‚Π΅Π»ΡŒΠ½ΠΎ. Π­Ρ‚ΠΎΡ‚ ΠΌΠ΅Ρ‚ΠΎΠ΄ извСстСн ΠΊΠ°ΠΊ TOAST …

ЀактичСски, для ΠΊΠ°ΠΆΠ΄ΠΎΠΉ Ρ‚Π°Π±Π»ΠΈΡ†Ρ‹ с Β«ΠΏΠΎΡ‚Π΅Π½Ρ†ΠΈΠ°Π»ΡŒΠ½ΠΎ большими» полями автоматичСски создаСтся парная Ρ‚Π°Π±Π»ΠΈΡ†Π° с Β«Π½Π°Ρ€Π΅Π·ΠΊΠΎΠΉΒ» ΠΊΠ°ΠΆΠ΄ΠΎΠΉ «большой» записи сСгмСнтами ΠΏΠΎ 2KB:

TOAST(
  chunk_id
    integer
, chunk_seq
    integer
, chunk_data
    bytea
, PRIMARY KEY(chunk_id, chunk_seq)
);

Π’ΠΎ Π΅ΡΡ‚ΡŒ Ссли Π½Π°ΠΌ приходится Π·Π°ΠΏΠΈΡΡ‹Π²Π°Ρ‚ΡŒ строку с «большим» Π·Π½Π°Ρ‡Π΅Π½ΠΈΠ΅ΠΌ data, Ρ‚ΠΎ Ρ€Π΅Π°Π»ΡŒΠ½Π°Ρ запись ΠΏΡ€ΠΎΠΈΠ·ΠΎΠΉΠ΄Π΅Ρ‚ Π½Π΅ Ρ‚ΠΎΠ»ΡŒΠΊΠΎ Π² ΠΎΡΠ½ΠΎΠ²Π½ΡƒΡŽ Ρ‚Π°Π±Π»ΠΈΡ†Ρƒ ΠΈ Π΅Π΅ PK, Π½ΠΎ ΠΈ Π² TOAST ΠΈ Π΅Π΅ PK.

УмСньшаСм TOAST-влияниС

Но Π±ΠΎΠ»ΡŒΡˆΠΈΠ½ΡΡ‚Π²ΠΎ записСй Ρƒ нас всС-Ρ‚Π°ΠΊΠΈ Π½Π΅ Ρ‚Π°ΠΊ ΡƒΠΆ ΠΈ Π²Π΅Π»ΠΈΠΊΠΈ, Π² 8KB Π΄ΠΎΠ»ΠΆΠ½Ρ‹ Π±Ρ‹ ΡƒΠΊΠ»Π°Π΄Ρ‹Π²Π°Ρ‚ΡŒΡΡ β€” ΠΊΠ°ΠΊ Π±Ρ‹ Π½Π° этом ΡΡΠΊΠΎΠ½ΠΎΠΌΠΈΡ‚ΡŒ?..

Π’ΡƒΡ‚ Π½Π°ΠΌ Π½Π° ΠΏΠΎΠΌΠΎΡ‰ΡŒ ΠΏΡ€ΠΈΡ…ΠΎΠ΄ΠΈΡ‚ Π°Ρ‚Ρ€ΠΈΠ±ΡƒΡ‚ STORAGE Ρƒ столбца Ρ‚Π°Π±Π»ΠΈΡ†Ρ‹:

  • EXTENDED допускаСт ΠΊΠ°ΠΊ сТатиС, Ρ‚Π°ΠΊ ΠΈ ΠΎΡ‚Π΄Π΅Π»ΡŒΠ½ΠΎΠ΅ Ρ…Ρ€Π°Π½Π΅Π½ΠΈΠ΅. Π­Ρ‚ΠΎ стандартный Π²Π°Ρ€ΠΈΠ°Π½Ρ‚ для Π±ΠΎΠ»ΡŒΡˆΠΈΠ½ΡΡ‚Π²Π° Ρ‚ΠΈΠΏΠΎΠ² Π΄Π°Π½Π½Ρ‹Ρ…, совмСстимых с TOAST. Π‘Π½Π°Ρ‡Π°Π»Π° происходит ΠΏΠΎΠΏΡ‹Ρ‚ΠΊΠ° Π²Ρ‹ΠΏΠΎΠ»Π½ΠΈΡ‚ΡŒ сТатиС, Π·Π°Ρ‚Π΅ΠΌ β€” сохранСниС Π²Π½Π΅ Ρ‚Π°Π±Π»ΠΈΡ†Ρ‹, Ссли строка всё Π΅Ρ‰Ρ‘ слишком Π²Π΅Π»ΠΈΠΊΠ°.
  • MAIN допускаСт сТатиС, Π½ΠΎ Π½Π΅ ΠΎΡ‚Π΄Π΅Π»ΡŒΠ½ΠΎΠ΅ Ρ…Ρ€Π°Π½Π΅Π½ΠΈΠ΅. (ЀактичСски, ΠΎΡ‚Π΄Π΅Π»ΡŒΠ½ΠΎΠ΅ Ρ…Ρ€Π°Π½Π΅Π½ΠΈΠ΅, Ρ‚Π΅ΠΌ Π½Π΅ ΠΌΠ΅Π½Π΅Π΅, Π±ΡƒΠ΄Π΅Ρ‚ Π²Ρ‹ΠΏΠΎΠ»Π½Π΅Π½ΠΎ для Ρ‚Π°ΠΊΠΈΡ… столбцов, Π½ΠΎ лишь ΠΊΠ°ΠΊ крайняя ΠΌΠ΅Ρ€Π°, ΠΊΠΎΠ³Π΄Π° Π½Π΅Ρ‚ Π΄Ρ€ΡƒΠ³ΠΎΠ³ΠΎ способа ΡƒΠΌΠ΅Π½ΡŒΡˆΠΈΡ‚ΡŒ строку Ρ‚Π°ΠΊ, Ρ‡Ρ‚ΠΎΠ±Ρ‹ ΠΎΠ½Π° ΠΏΠΎΠΌΠ΅Ρ‰Π°Π»Π°ΡΡŒ Π½Π° страницС.)

ЀактичСски, это Ρ€ΠΎΠ²Π½ΠΎ Ρ‚ΠΎ, Ρ‡Ρ‚ΠΎ Π½Π°ΠΌ Π½ΡƒΠΆΠ½ΠΎ для тСкста β€” максимально ΡΠΆΠ°Ρ‚ΡŒ, ΠΈ ΡƒΠΆ Ссли совсСм Π½ΠΈΠΊΠ°ΠΊ Π½Π΅ Π²Π»Π΅Π·Π»ΠΎ β€” вынСсти Π² TOAST. Π‘Π΄Π΅Π»Π°Ρ‚ΡŒ это ΠΌΠΎΠΆΠ½ΠΎ прямо Β«Π½Π° Π»Π΅Ρ‚ΡƒΒ», ΠΎΠ΄Π½ΠΎΠΉ ΠΊΠΎΠΌΠ°Π½Π΄ΠΎΠΉ:

ALTER TABLE rawdata_orig ALTER COLUMN data SET STORAGE MAIN;

Как ΠΎΡ†Π΅Π½ΠΈΡ‚ΡŒ эффСкт

ΠŸΠΎΡΠΊΠΎΠ»ΡŒΠΊΡƒ ΠΊΠ°ΠΆΠ΄Ρ‹ΠΉ дСнь ΠΏΠΎΡ‚ΠΎΠΊ Π΄Π°Π½Π½Ρ‹Ρ… мСняСтся, ΠΌΡ‹ Π½Π΅ ΠΌΠΎΠΆΠ΅ΠΌ ΡΡ€Π°Π²Π½ΠΈΠ²Π°Ρ‚ΡŒ Π°Π±ΡΠΎΠ»ΡŽΡ‚Π½Ρ‹Π΅ Ρ†ΠΈΡ„Ρ€Ρ‹, Π½ΠΎ Π² ΠΎΡ‚Π½ΠΎΡΠΈΡ‚Π΅Π»ΡŒΠ½Ρ‹Ρ… Ρ‡Π΅ΠΌ ΠΌΠ΅Π½ΡŒΡˆΡƒΡŽ долю ΠΌΡ‹ записали Π² TOAST β€” Ρ‚Π΅ΠΌ Π»ΡƒΡ‡ΡˆΠ΅. Но Ρ‚ΡƒΡ‚ Π΅ΡΡ‚ΡŒ ΠΎΠΏΠ°ΡΠ½ΠΎΡΡ‚ΡŒ β€” Ρ‡Π΅ΠΌ большС Ρƒ нас «физичСский» объСм ΠΊΠ°ΠΆΠ΄ΠΎΠΉ ΠΎΡ‚Π΄Π΅Π»ΡŒΠ½ΠΎΠΉ записи, Ρ‚Π΅ΠΌ Β«ΡˆΠΈΡ€Π΅Β» становится индСкс, ΠΏΠΎΡ‚ΠΎΠΌΡƒ ΠΊΠ°ΠΊ приходится ΠΏΠΎΠΊΡ€Ρ‹Π²Π°Ρ‚ΡŒ большСС количСство страниц Π΄Π°Π½Π½Ρ‹Ρ….

БСкция Π΄ΠΎ ΠΈΠ·ΠΌΠ΅Π½Π΅Π½ΠΈΠΉ:

heap  = 37GB (39%)
TOAST = 54GB (57%)
PK    =  4GB ( 4%)

БСкция послС ΠΈΠ·ΠΌΠ΅Π½Π΅Π½ΠΈΠΉ:

heap  = 37GB (67%)
TOAST = 16GB (29%)
PK    =  2GB ( 4%)

ЀактичСски, ΠΌΡ‹ стали ΠΏΠΈΡΠ°Ρ‚ΡŒ Π² TOAST Π² 2 Ρ€Π°Π·Π° Ρ€Π΅ΠΆΠ΅, Ρ‡Ρ‚ΠΎ Ρ€Π°Π·Π³Ρ€ΡƒΠ·ΠΈΠ»ΠΎ Π½Π΅ Ρ‚ΠΎΠ»ΡŒΠΊΠΎ диск, Π½ΠΎ ΠΈ CPU:

Π­ΠΊΠΎΠ½ΠΎΠΌΠΈΠΌ ΠΊΠΎΠΏΠ΅Π΅Ρ‡ΠΊΡƒ Π½Π° Π±ΠΎΠ»ΡŒΡˆΠΈΡ… ΠΎΠ±ΡŠΠ΅ΠΌΠ°Ρ… Π² PostgreSQL
Π­ΠΊΠΎΠ½ΠΎΠΌΠΈΠΌ ΠΊΠΎΠΏΠ΅Π΅Ρ‡ΠΊΡƒ Π½Π° Π±ΠΎΠ»ΡŒΡˆΠΈΡ… ΠΎΠ±ΡŠΠ΅ΠΌΠ°Ρ… Π² PostgreSQL
Π—Π°ΠΌΠ΅Ρ‡Ρƒ, Ρ‡Ρ‚ΠΎ ΠΌΡ‹ Π΅Ρ‰Π΅ ΠΈ Β«Ρ‡ΠΈΡ‚Π°Ρ‚ΡŒΒ» диск стали мСньшС, Π½Π΅ Ρ‚ΠΎΠ»ΡŒΠΊΠΎ Β«ΠΏΠΈΡΠ°Ρ‚ΡŒΒ» β€” ΠΏΠΎΡΠΊΠΎΠ»ΡŒΠΊΡƒ ΠΏΡ€ΠΈ вставкС записи Π² ΠΊΠ°ΠΊΡƒΡŽ-Ρ‚ΠΎ Ρ‚Π°Π±Π»ΠΈΡ†Ρƒ приходится Β«Π²Ρ‹Ρ‡ΠΈΡ‚Ρ‹Π²Π°Ρ‚ΡŒΒ» Π΅Ρ‰Π΅ ΠΈ Ρ‡Π°ΡΡ‚ΡŒ Π΄Π΅Ρ€Π΅Π²Π° ΠΊΠ°ΠΆΠ΄ΠΎΠ³ΠΎ ΠΈΠ· индСксов, Ρ‡Ρ‚ΠΎΠ±Ρ‹ ΠΎΠΏΡ€Π΅Π΄Π΅Π»ΠΈΡ‚ΡŒ Π΅Π΅ Π±ΡƒΠ΄ΡƒΡ‰ΡƒΡŽ ΠΏΠΎΠ·ΠΈΡ†ΠΈΡŽ Π² Π½ΠΈΡ….

ΠšΠΎΠΌΡƒ Π½Π° PostgreSQL 11 ΠΆΠΈΡ‚ΡŒ Ρ…ΠΎΡ€ΠΎΡˆΠΎ

ПослС обновлСния Π΄ΠΎ PG11 ΠΌΡ‹ Ρ€Π΅ΡˆΠΈΠ»ΠΈ ΠΏΡ€ΠΎΠ΄ΠΎΠ»ΠΆΠΈΡ‚ΡŒ Β«Ρ‚ΡŽΠ½ΠΈΠ½Π³Β» TOAST ΠΈ ΠΎΠ±Ρ€Π°Ρ‚ΠΈΠ»ΠΈ Π²Π½ΠΈΠΌΠ°Π½ΠΈΠ΅, Ρ‡Ρ‚ΠΎ начиная с этой вСрсии стал доступСн для настройки ΠΏΠ°Ρ€Π°ΠΌΠ΅Ρ‚Ρ€ toast_tuple_target:

Код ΠΎΠ±Ρ€Π°Π±ΠΎΡ‚ΠΊΠΈ TOAST срабатываСт, Ρ‚ΠΎΠ»ΡŒΠΊΠΎ ΠΊΠΎΠ³Π΄Π° Π·Π½Π°Ρ‡Π΅Π½ΠΈΠ΅ строки, ΠΊΠΎΡ‚ΠΎΡ€ΠΎΠ΅ Π΄ΠΎΠ»ΠΆΠ½ΠΎ Ρ…Ρ€Π°Π½ΠΈΡ‚ΡŒΡΡ Π² Ρ‚Π°Π±Π»ΠΈΡ†Π΅, ΠΏΠΎ Ρ€Π°Π·ΠΌΠ΅Ρ€Ρƒ большС, Ρ‡Π΅ΠΌ TOAST_TUPLE_THRESHOLD Π±Π°ΠΉΡ‚ (ΠΎΠ±Ρ‹Ρ‡Π½ΠΎ это 2 Кб). Код TOAST Π±ΡƒΠ΄Π΅Ρ‚ ΡΠΆΠΈΠΌΠ°Ρ‚ΡŒ ΠΈ/ΠΈΠ»ΠΈ Π²Ρ‹Π½ΠΎΡΠΈΡ‚ΡŒ значСния поля Π·Π° ΠΏΡ€Π΅Π΄Π΅Π»Ρ‹ Ρ‚Π°Π±Π»ΠΈΡ†Ρ‹ Π΄ΠΎ Ρ‚Π΅Ρ… ΠΏΠΎΡ€, ΠΏΠΎΠΊΠ° Π·Π½Π°Ρ‡Π΅Π½ΠΈΠ΅ строки Π½Π΅ станСт мСньшС TOAST_TUPLE_TARGET Π±Π°ΠΉΡ‚ (пСрСмСнная Π²Π΅Π»ΠΈΡ‡ΠΈΠ½Π°, Ρ‚Π°ΠΊ ΠΆΠ΅ ΠΎΠ±Ρ‹Ρ‡Π½ΠΎ 2 Кб) ΠΈΠ»ΠΈ ΡƒΠΌΠ΅Π½ΡŒΡˆΠΈΡ‚ΡŒ ΠΎΠ±ΡŠΡ‘ΠΌ станСт Π½Π΅Π²ΠΎΠ·ΠΌΠΎΠΆΠ½ΠΎ.

ΠœΡ‹ Ρ€Π΅ΡˆΠΈΠ»ΠΈ, Ρ‡Ρ‚ΠΎ Π΄Π°Π½Π½Ρ‹Π΅ Ρƒ нас ΠΎΠ±Ρ‹Ρ‡Π½ΠΎ Π±Ρ‹Π²Π°ΡŽΡ‚ ΠΈΠ»ΠΈ ΡƒΠΆ «совсСм ΠΊΠΎΡ€ΠΎΡ‚ΠΊΠΈΠ΅Β» ΠΈΠ»ΠΈ сразу Β«ΠΎΡ‡Π΅Π½ΡŒ ΡƒΠΆ Π΄Π»ΠΈΠ½Π½Ρ‹Π΅Β», поэтому Ρ€Π΅ΡˆΠΈΠ»ΠΈ ΠΎΠ³Ρ€Π°Π½ΠΈΡ‡ΠΈΡ‚ΡŒΡΡ минимально-Π²ΠΎΠ·ΠΌΠΎΠΆΠ½Ρ‹ΠΌ Π·Π½Π°Ρ‡Π΅Π½ΠΈΠ΅ΠΌ:

ALTER TABLE rawplan_orig SET (toast_tuple_target = 128);

Π”Π°Π²Π°ΠΉΡ‚Π΅ посмотрим, ΠΊΠ°ΠΊ Π½ΠΎΠ²Ρ‹Π΅ настройки сказались Π½Π° Π·Π°Π³Ρ€ΡƒΠ·ΠΊΠ΅ диска послС пСрСнастройки:

Π­ΠΊΠΎΠ½ΠΎΠΌΠΈΠΌ ΠΊΠΎΠΏΠ΅Π΅Ρ‡ΠΊΡƒ Π½Π° Π±ΠΎΠ»ΡŒΡˆΠΈΡ… ΠΎΠ±ΡŠΠ΅ΠΌΠ°Ρ… Π² PostgreSQL
НСплохо! БрСдняя ΠΎΡ‡Π΅Ρ€Π΅Π΄ΡŒ ΠΊ диску ΡΠΎΠΊΡ€Π°Ρ‚ΠΈΠ»Π°ΡΡŒ ΠΏΡ€ΠΈΠΌΠ΅Ρ€Π½ΠΎ Π² 1.5 Ρ€Π°Π·Π°, Π° Β«Π·Π°Π½ΡΡ‚ΠΎΡΡ‚ΡŒΒ» диска β€” ΠΏΡ€ΠΎΡ†Π΅Π½Ρ‚ΠΎΠ² Π½Π° 20! Но ΠΌΠΎΠΆΠ΅Ρ‚, это ΠΊΠ°ΠΊ-Ρ‚ΠΎ сказалось Π½Π° CPU?

Π­ΠΊΠΎΠ½ΠΎΠΌΠΈΠΌ ΠΊΠΎΠΏΠ΅Π΅Ρ‡ΠΊΡƒ Π½Π° Π±ΠΎΠ»ΡŒΡˆΠΈΡ… ΠΎΠ±ΡŠΠ΅ΠΌΠ°Ρ… Π² PostgreSQL
По ΠΊΡ€Π°ΠΉΠ½Π΅ΠΉ ΠΌΠ΅Ρ€Π΅, Ρ…ΡƒΠΆΠ΅ Ρ‚ΠΎΡ‡Π½ΠΎ Π½Π΅ стало. Π₯отя, слоТно ΡΡƒΠ΄ΠΈΡ‚ΡŒ, Ссли Π΄Π°ΠΆΠ΅ Ρ‚Π°ΠΊΠΈΠ΅ ΠΎΠ±ΡŠΠ΅ΠΌΡ‹ всС Ρ€Π°Π²Π½ΠΎ Π½Π΅ ΠΌΠΎΠ³ΡƒΡ‚ ΠΏΠΎΠ΄Π½ΡΡ‚ΡŒ ΡΡ€Π΅Π΄Π½ΡŽΡŽ Π·Π°Π³Ρ€ΡƒΠ·ΠΊΡƒ CPU Π²Ρ‹ΡˆΠ΅ 5%.

ΠžΡ‚ ΠΏΠ΅Ρ€Π΅ΠΌΠ΅Π½Ρ‹ мСст слагаСмых сумма… мСняСтся!

Как извСстно, ΠΊΠΎΠΏΠ΅ΠΉΠΊΠ° Ρ€ΡƒΠ±Π»ΡŒ Π±Π΅Ρ€Π΅ΠΆΠ΅Ρ‚, ΠΈ ΠΏΡ€ΠΈ Π½Π°ΡˆΠΈΡ… ΠΎΠ±ΡŠΠ΅ΠΌΠ°Ρ… хранСния порядка 10TB/мСсяц Π΄Π°ΠΆΠ΅ нСбольшая оптимизация способна Π΄Π°Ρ‚ΡŒ Π½Π΅ΠΏΠ»ΠΎΡ…ΠΎΠΉ ΠΏΡ€ΠΎΡ„ΠΈΡ‚. ΠŸΠΎΡΡ‚ΠΎΠΌΡƒ ΠΌΡ‹ ΠΎΠ±Ρ€Π°Ρ‚ΠΈΠ»ΠΈ Π²Π½ΠΈΠΌΠ°Π½ΠΈΠ΅ Π½Π° Ρ„ΠΈΠ·ΠΈΡ‡Π΅ΡΠΊΡƒΡŽ структуру своих Π΄Π°Π½Π½Ρ‹Ρ… β€” ΠΊΠ°ΠΊ ΠΊΠΎΠ½ΠΊΡ€Π΅Ρ‚Π½ΠΎ Β«ΡƒΠ»ΠΎΠΆΠ΅Π½Ρ‹Β» поля Π²Π½ΡƒΡ‚Ρ€ΠΈ записи ΠΊΠ°ΠΆΠ΄ΠΎΠΉ ΠΈΠ· Ρ‚Π°Π±Π»ΠΈΡ†.

ΠŸΠΎΡ‚ΠΎΠΌΡƒ Ρ‡Ρ‚ΠΎ ΠΈΠ·-Π·Π° выравнивания Π΄Π°Π½Π½Ρ‹Ρ… это Π²ΠΏΡ€ΡΠΌΡƒΡŽ влияСт Π½Π° Ρ€Π΅Π·ΡƒΠ»ΡŒΡ‚ΠΈΡ€ΡƒΡŽΡ‰ΠΈΠΉ объСм:

МногиС Π°Ρ€Ρ…ΠΈΡ‚Π΅ΠΊΡ‚ΡƒΡ€Ρ‹ ΠΏΡ€Π΅Π΄ΡƒΡΠΌΠ°Ρ‚Ρ€ΠΈΠ²Π°ΡŽΡ‚ Π²Ρ‹Ρ€Π°Π²Π½ΠΈΠ²Π°Π½ΠΈΠ΅ Π΄Π°Π½Π½Ρ‹Ρ… ΠΏΠΎ Π³Ρ€Π°Π½ΠΈΡ†Π°ΠΌ ΠΌΠ°ΡˆΠΈΠ½Π½Ρ‹Ρ… слов. НапримСр, Π½Π° 32-Π±ΠΈΡ‚Π½ΠΎΠΉ систСмС x86 Ρ†Π΅Π»Ρ‹Π΅ числа (Ρ‚ΠΈΠΏ integer, Π·Π°Π½ΠΈΠΌΠ°Π΅Ρ‚ 4 Π±Π°ΠΉΡ‚Π°) Π±ΡƒΠ΄ΡƒΡ‚ Π²Ρ‹Ρ€ΠΎΠ²Π½Π΅Π½Ρ‹ ΠΏΠΎ Π³Ρ€Π°Π½ΠΈΡ†Π΅ 4-Π±Π°ΠΉΡ‚Π½Ρ‹Ρ… слов, ΠΊΠ°ΠΊ ΠΈ числа с ΠΏΠ»Π°Π²Π°ΡŽΡ‰Π΅ΠΉ Ρ‚ΠΎΡ‡ΠΊΠΎΠΉ Π΄Π²ΠΎΠΉΠ½ΠΎΠΉ точности (Ρ‚ΠΈΠΏ double precision, 8 Π±Π°ΠΉΡ‚). А Π½Π° 64-Π±ΠΈΡ‚Π½ΠΎΠΉ систСмС значСния double Π±ΡƒΠ΄ΡƒΡ‚ Π²Ρ‹Ρ€ΠΎΠ²Π½Π΅Π½Ρ‹ ΠΏΠΎ Π³Ρ€Π°Π½ΠΈΡ†Π΅ 8-Π±Π°ΠΉΡ‚Π½Ρ‹Ρ… слов. Π­Ρ‚ΠΎ Π΅Ρ‰Π΅ ΠΎΠ΄Π½Π° ΠΏΡ€ΠΈΡ‡ΠΈΠ½Π° нСсовмСстимости.

Из-Π·Π° выравнивания Ρ€Π°Π·ΠΌΠ΅Ρ€ Ρ‚Π°Π±Π»ΠΈΡ‡Π½ΠΎΠΉ строки зависит ΠΎΡ‚ порядка располоТСния ΠΏΠΎΠ»Π΅ΠΉ. ΠžΠ±Ρ‹Ρ‡Π½ΠΎ этот эффСкт Π½Π΅ сильно Π·Π°ΠΌΠ΅Ρ‚Π΅Π½, Π½ΠΎ Π² Π½Π΅ΠΊΠΎΡ‚ΠΎΡ€Ρ‹Ρ… случаях ΠΎΠ½ ΠΌΠΎΠΆΠ΅Ρ‚ привСсти ΠΊ сущСствСнному ΡƒΠ²Π΅Π»ΠΈΡ‡Π΅Π½ΠΈΡŽ Ρ€Π°Π·ΠΌΠ΅Ρ€Π°. НапримСр, Ссли Ρ€Π°ΡΠΏΠΎΠ»Π°Π³Π°Ρ‚ΡŒ поля Ρ‚ΠΈΠΏΠΎΠ² char(1) ΠΈ integer Π²ΠΏΠ΅Ρ€Π΅ΠΌΠ΅ΡˆΠΊΡƒ, ΠΌΠ΅ΠΆΠ΄Ρƒ Π½ΠΈΠΌΠΈ, ΠΊΠ°ΠΊ ΠΏΡ€Π°Π²ΠΈΠ»ΠΎ, Π±ΡƒΠ΄Π΅Ρ‚ Π²ΠΏΡƒΡΡ‚ΡƒΡŽ ΠΏΡ€ΠΎΠΏΠ°Π΄Π°Ρ‚ΡŒ 3 Π±Π°ΠΉΡ‚Π°.

Π”Π°Π²Π°ΠΉΡ‚Π΅ Π½Π°Ρ‡Π½Π΅ΠΌ с синтСтичСских ΠΌΠΎΠ΄Π΅Π»Π΅ΠΉ:

SELECT pg_column_size(ROW(
  '0000-0000-0000-0000-0000-0000-0000-0000'::uuid
, 0::smallint
, '2019-01-01'::date
));
-- 48 Π±Π°ΠΉΡ‚

SELECT pg_column_size(ROW(
  '2019-01-01'::date
, '0000-0000-0000-0000-0000-0000-0000-0000'::uuid
, 0::smallint
));
-- 46 Π±Π°ΠΉΡ‚

ΠžΡ‚ΠΊΡƒΠ΄Π° Π½Π°Π±Π΅ΠΆΠ°Π»Π° ΠΏΠ°Ρ€Π° Π»ΠΈΡˆΠ½ΠΈΡ… Π±Π°ΠΉΡ‚ Π² ΠΏΠ΅Ρ€Π²ΠΎΠΌ случаС? ВсС просто β€” 2-Π±Π°ΠΉΡ‚ΠΎΠ²Ρ‹ΠΉ smallint выравниваСтся ΠΏΠΎ 4-Π±Π°ΠΉΡ‚ΠΎΠ²ΠΎΠΉ Π³Ρ€Π°Π½ΠΈΡ†Π΅ ΠΏΠ΅Ρ€Π΅Π΄ ΡΠ»Π΅Π΄ΡƒΡŽΡ‰ΠΈΠΌ ΠΏΠΎΠ»Π΅ΠΌ, Π° ΠΊΠΎΠ³Π΄Π° стоит послСдним β€” Π²Ρ‹Ρ€Π°Π²Π½ΠΈΠ²Π°Ρ‚ΡŒ Π½Π΅Ρ‡Π΅Π³ΠΎ ΠΈ Π½Π΅Π·Π°Ρ‡Π΅ΠΌ.

Π’ Ρ‚Π΅ΠΎΡ€ΠΈΠΈ β€” всС Ρ…ΠΎΡ€ΠΎΡˆΠΎ ΠΈ ΠΌΠΎΠΆΠ½ΠΎ ΠΏΠ΅Ρ€Π΅ΡΡ‚Π°Π²Π»ΡΡ‚ΡŒ поля ΠΊΠ°ΠΊ ΡƒΠ³ΠΎΠ΄Π½ΠΎ. Π”Π°Π²Π°ΠΉΡ‚Π΅ ΠΏΡ€ΠΎΠ²Π΅Ρ€ΠΈΠΌ Π½Π° Ρ€Π΅Π°Π»ΡŒΠ½Ρ‹Ρ… Π΄Π°Π½Π½Ρ‹Ρ… Π½Π° ΠΏΡ€ΠΈΠΌΠ΅Ρ€Π΅ ΠΎΠ΄Π½ΠΎΠΉ ΠΈΠ· Ρ‚Π°Π±Π»ΠΈΡ†, суточная сСкция ΠΊΠΎΡ‚ΠΎΡ€ΠΎΠΉ Π·Π°Π½ΠΈΠΌΠ°Π΅Ρ‚ ΠΏΠΎ 10-15GB.

Π˜ΡΡ…ΠΎΠ΄Π½Π°Ρ структура:

CREATE TABLE public.plan_20190220
(
-- УнаслСдована from table plan:  pack uuid NOT NULL,
-- УнаслСдована from table plan:  recno smallint NOT NULL,
-- УнаслСдована from table plan:  host uuid,
-- УнаслСдована from table plan:  ts timestamp with time zone,
-- УнаслСдована from table plan:  exectime numeric(32,3),
-- УнаслСдована from table plan:  duration numeric(32,3),
-- УнаслСдована from table plan:  bufint bigint,
-- УнаслСдована from table plan:  bufmem bigint,
-- УнаслСдована from table plan:  bufdsk bigint,
-- УнаслСдована from table plan:  apn uuid,
-- УнаслСдована from table plan:  ptr uuid,
-- УнаслСдована from table plan:  dt date,
  CONSTRAINT plan_20190220_pkey PRIMARY KEY (pack, recno),
  CONSTRAINT chck_ptr CHECK (ptr IS NOT NULL),
  CONSTRAINT plan_20190220_dt_check CHECK (dt = '2019-02-20'::date)
)
INHERITS (public.plan)

БСкция послС смСны порядка столбцов β€” Ρ€ΠΎΠ²Π½ΠΎ Ρ‚Π΅ ΠΆΠ΅ поля, Ρ‚ΠΎΠ»ΡŒΠΊΠΎ порядок Π΄Ρ€ΡƒΠ³ΠΎΠΉ:

CREATE TABLE public.plan_20190221
(
-- УнаслСдована from table plan:  dt date NOT NULL,
-- УнаслСдована from table plan:  ts timestamp with time zone,
-- УнаслСдована from table plan:  pack uuid NOT NULL,
-- УнаслСдована from table plan:  recno smallint NOT NULL,
-- УнаслСдована from table plan:  host uuid,
-- УнаслСдована from table plan:  apn uuid,
-- УнаслСдована from table plan:  ptr uuid,
-- УнаслСдована from table plan:  bufint bigint,
-- УнаслСдована from table plan:  bufmem bigint,
-- УнаслСдована from table plan:  bufdsk bigint,
-- УнаслСдована from table plan:  exectime numeric(32,3),
-- УнаслСдована from table plan:  duration numeric(32,3),
  CONSTRAINT plan_20190221_pkey PRIMARY KEY (pack, recno),
  CONSTRAINT chck_ptr CHECK (ptr IS NOT NULL),
  CONSTRAINT plan_20190221_dt_check CHECK (dt = '2019-02-21'::date)
)
INHERITS (public.plan)

ΠžΠ±Ρ‰ΠΈΠΉ объСм сСкции опрСдСляСтся количСством Β«Ρ„Π°ΠΊΡ‚ΠΎΠ²Β» ΠΈ зависит Ρ‚ΠΎΠ»ΡŒΠΊΠΎ ΠΎΡ‚ Π²Π½Π΅ΡˆΠ½ΠΈΡ… процСссов, поэтому ΠΏΠΎΠ΄Π΅Π»ΠΈΠΌ Ρ€Π°Π·ΠΌΠ΅Ρ€ heap (pg_relation_size) Π½Π° количСство записСй Π² Π½Π΅ΠΉ β€” Ρ‚ΠΎ Π΅ΡΡ‚ΡŒ ΠΏΠΎΠ»ΡƒΡ‡ΠΈΠΌ срСдний Ρ€Π°Π·ΠΌΠ΅Ρ€ Ρ€Π΅Π°Π»ΡŒΠ½ΠΎΠΉ Ρ…Ρ€Π°Π½ΠΈΠΌΠΎΠΉ записи:

Π­ΠΊΠΎΠ½ΠΎΠΌΠΈΠΌ ΠΊΠΎΠΏΠ΅Π΅Ρ‡ΠΊΡƒ Π½Π° Π±ΠΎΠ»ΡŒΡˆΠΈΡ… ΠΎΠ±ΡŠΠ΅ΠΌΠ°Ρ… Π² PostgreSQL
ΠœΠΈΠ½ΡƒΡ 6% объСма, ΠΎΡ‚Π»ΠΈΡ‡Π½ΠΎ!

Но всС, ΠΊΠΎΠ½Π΅Ρ‡Π½ΠΎ, Π½Π΅ Π½Π°ΡΡ‚ΠΎΠ»ΡŒΠΊΠΎ Ρ€Π°Π΄ΡƒΠΆΠ½ΠΎ β€” вСдь Π² индСксах-Ρ‚ΠΎ порядок ΠΏΠΎΠ»Π΅ΠΉ ΠΌΡ‹ ΠΈΠ·ΠΌΠ΅Π½ΠΈΡ‚ΡŒ Π½Π΅ ΠΌΠΎΠΆΠ΅ΠΌ, Π° поэтому Β«Π² Ρ†Π΅Π»ΠΎΠΌΒ» (pg_total_relation_size)…

Π­ΠΊΠΎΠ½ΠΎΠΌΠΈΠΌ ΠΊΠΎΠΏΠ΅Π΅Ρ‡ΠΊΡƒ Π½Π° Π±ΠΎΠ»ΡŒΡˆΠΈΡ… ΠΎΠ±ΡŠΠ΅ΠΌΠ°Ρ… Π² PostgreSQL
… всС-Ρ‚Π°ΠΊΠΈ ΠΈ Ρ‚ΡƒΡ‚ сэкономили 1.5%, Π½Π΅ ΠΈΠ·ΠΌΠ΅Π½ΠΈΠ² Π½ΠΈ строчки ΠΊΠΎΠ΄Π°. Π’Π°ΠΊΠΈ Π΄Π°!

Π­ΠΊΠΎΠ½ΠΎΠΌΠΈΠΌ ΠΊΠΎΠΏΠ΅Π΅Ρ‡ΠΊΡƒ Π½Π° Π±ΠΎΠ»ΡŒΡˆΠΈΡ… ΠΎΠ±ΡŠΠ΅ΠΌΠ°Ρ… Π² PostgreSQL

Π—Π°ΠΌΠ΅Ρ‡Ρƒ, Ρ‡Ρ‚ΠΎ ΠΏΡ€ΠΈΠ²Π΅Π΄Π΅Π½Π½Ρ‹ΠΉ Π²Ρ‹ΡˆΠ΅ Π²Π°Ρ€ΠΈΠ°Π½Ρ‚ расстановки ΠΏΠΎΠ»Π΅ΠΉ β€” Π½Π΅ Ρ„Π°ΠΊΡ‚, Ρ‡Ρ‚ΠΎ самый ΠΎΠΏΡ‚ΠΈΠΌΠ°Π»ΡŒΠ½Ρ‹ΠΉ. ΠŸΠΎΡ‚ΠΎΠΌΡƒ Ρ‡Ρ‚ΠΎ Π½Π΅ΠΊΠΎΡ‚ΠΎΡ€Ρ‹Π΅ Π±Π»ΠΎΠΊΠΈ ΠΏΠΎΠ»Π΅ΠΉ Π½Π΅ хочСтся Β«Ρ€Π°Π·Ρ€Ρ‹Π²Π°Ρ‚ΡŒΒ» ΡƒΠΆΠ΅ ΠΏΠΎ эстСтичСским сообраТСниям β€” Π½Π°ΠΏΡ€ΠΈΠΌΠ΅Ρ€, ΠΏΠ°Ρ€Ρƒ (pack, recno), которая являСтся PK для этой Ρ‚Π°Π±Π»ΠΈΡ†Ρ‹.

Π’ Ρ†Π΅Π»ΠΎΠΌ ΠΆΠ΅, ΠΎΠΏΡ€Π΅Π΄Π΅Π»Π΅Π½ΠΈΠ΅ «минимальной» расстановки ΠΏΠΎΠ»Π΅ΠΉ β€” это достаточно простая «пСрСборная» Π·Π°Π΄Π°Ρ‡Π°. ΠŸΠΎΡΡ‚ΠΎΠΌΡƒ Π²Ρ‹ ΠΌΠΎΠΆΠ΅Ρ‚Π΅ Π½Π° своих Π΄Π°Π½Π½Ρ‹Ρ… ΠΏΠΎΠ»ΡƒΡ‡ΠΈΡ‚ΡŒ Ρ€Π΅Π·ΡƒΠ»ΡŒΡ‚Π°Ρ‚Ρ‹ Π΄Π°ΠΆΠ΅ Π»ΡƒΡ‡ΡˆΠ΅, Ρ‡Π΅ΠΌ Ρƒ нас β€” ΠΏΠΎΠΏΡ€ΠΎΠ±ΡƒΠΉΡ‚Π΅!

Π˜ΡΡ‚ΠΎΡ‡Π½ΠΈΠΊ: habr.com