Π’Ρ‹Π³Ρ€ΡƒΠ·ΠΊΠ° Π»ΠΎΠ³Π° PostgreSQL с ΠΎΠ±Π»Π°ΠΊΠ° AWS

Или Π½Π΅ΠΌΠ½ΠΎΠ³ΠΎ ΠΏΡ€ΠΈΠΊΠ»Π°Π΄Π½ΠΎΠΉ тСтрисологии.
Всё Π½ΠΎΠ²ΠΎΠ΅- Ρ…ΠΎΡ€ΠΎΡˆΠΎ Π·Π°Π±Ρ‹Ρ‚ΠΎΠ΅ староС.
Π­ΠΏΠΈΠ³Ρ€Π°Ρ„Ρ‹.
Π’Ρ‹Π³Ρ€ΡƒΠ·ΠΊΠ° Π»ΠΎΠ³Π° PostgreSQL с ΠΎΠ±Π»Π°ΠΊΠ° AWS

ΠŸΠΎΡΡ‚Π°Π½ΠΎΠ²ΠΊΠ° Π·Π°Π΄Π°Ρ‡ΠΈ

НСобходимо пСриодичСски Π·Π°Π³Ρ€ΡƒΠΆΠ°Ρ‚ΡŒ Ρ‚Π΅ΠΊΡƒΡ‰ΠΈΠΉ Π»ΠΎΠ³-Ρ„Π°ΠΉΠ» PostgreSQL ΠΈΠ· ΠΎΠ±Π»Π°ΠΊΠ° AWS Π½Π° Π»ΠΎΠΊΠ°Π»ΡŒΠ½Ρ‹ΠΉ Linux хост. НС Π² Ρ€Π΅Π°Π»ΡŒΠ½ΠΎΠΌ Π²Ρ€Π΅ΠΌΠ΅Π½ΠΈ, Π½ΠΎ, скаТСм Ρ‚Π°ΠΊ, с нСбольшой Π·Π°Π΄Π΅Ρ€ΠΆΠΊΠΎΠΉ.
ΠŸΠ΅Ρ€ΠΈΠΎΠ΄ Π·Π°Π³Ρ€ΡƒΠ·ΠΊΠΈ обновлСния Π»ΠΎΠ³-Ρ„Π°ΠΉΠ»Π° β€” 5 ΠΌΠΈΠ½ΡƒΡ‚.
Π›ΠΎΠ³-Ρ„Π°ΠΉΠ», Π² AWS, ротируСтся ΠΊΠ°ΠΆΠ΄Ρ‹ΠΉ час.

Π˜ΡΠΏΠΎΠ»ΡŒΠ·ΡƒΠ΅ΠΌΡ‹Π΅ инструмСнты

Для Π·Π°Π³Ρ€ΡƒΠ·ΠΊΠΈ Π»ΠΎΠ³-Ρ„Π°ΠΉΠ»Π° Π½Π° хост ΠΈΡΠΏΠΎΠ»ΡŒΠ·ΡƒΠ΅Ρ‚ΡΡ bash-скрипт, Π²Ρ‹Π·Ρ‹Π²Π°ΡŽΡ‰ΠΈΠΉ AWS API Β«aws rds download-db-log-file-portionΒ».

ΠŸΠ°Ρ€Π°ΠΌΠ΅Ρ‚Ρ€Ρ‹:

  • —db-instance-identifier: Имя инстанса Π² AWS;
  • —log-file-name: имя Ρ‚Π΅ΠΊΡƒΡ‰Π΅Π³ΠΎ сформированного Π»ΠΎΠ³-Ρ„Π°ΠΉΠ»Π°
  • —max-item: ΠžΠ±Ρ‰Π΅Π΅ количСство элСмСнтов, Π²ΠΎΠ·Π²Ρ€Π°Ρ‰Π°Π΅ΠΌΡ‹Ρ… Π² Π²Ρ‹Ρ…ΠΎΠ΄Π½Ρ‹Ρ… Π΄Π°Π½Π½Ρ‹Ρ… ΠΊΠΎΠΌΠ°Π½Π΄Ρ‹.Π Π°Π·ΠΌΠ΅Ρ€ ΠΏΠΎΡ€Ρ†ΠΈΠΈ Π·Π°Π³Ρ€ΡƒΠΆΠ°Π΅ΠΌΠΎΠ³ΠΎ Ρ„Π°ΠΉΠ»Π°.
  • —starting-token: ΠœΠ΅Ρ‚ΠΊΠ° Π½Π°Ρ‡Π°Π»ΡŒΠ½ΠΎΠΉ ΠΏΠΎΡ€Ρ†ΠΈΠΈ

Π’ Π΄Π°Π½Π½ΠΎΠΌ ΠΊΠΎΠ½ΠΊΡ€Π΅Ρ‚Π½ΠΎΠΌ случаС, Π·Π°Π΄Π°Ρ‡Π° Π·Π°Π³Ρ€ΡƒΠ·ΠΊΠΈ Π»ΠΎΠ³ΠΎΠ² Π²ΠΎΠ·Π½ΠΈΠΊΠ»Π° ΠΏΠΎ Ρ…ΠΎΠ΄Ρƒ Ρ€Π°Π±ΠΎΡ‚ Π½Π°Π΄ ΠΌΠΎΠ½ΠΈΡ‚ΠΎΡ€ΠΈΠ½Π³ΠΎΠΌ ΠΏΡ€ΠΎΠΈΠ·Π²ΠΎΠ΄ΠΈΡ‚Π΅Π»ΡŒΠ½ΠΎΡΡ‚ΠΈ запросов PostgreSQL.

Π”Π° ΠΈ просто β€” интСрСсная Π·Π°Π΄Π°Ρ‡Π°, для Ρ‚Ρ€Π΅Π½ΠΈΡ€ΠΎΠ²ΠΊΠΈ ΠΈ разнообразия Π² Ρ…ΠΎΠ΄Π΅ Ρ€Π°Π±ΠΎΡ‡Π΅Π³ΠΎ Π²Ρ€Π΅ΠΌΠ΅Π½ΠΈ.
ΠŸΡ€Π΅Π΄ΠΏΠΎΠ»ΠΎΠΆΡƒ, Ρ‡Ρ‚ΠΎ Π·Π°Π΄Π°Ρ‡Π° Π² силу обыдСнности ΡƒΠΆΠ΅ Ρ€Π΅ΡˆΠ΅Π½Π°. Но быстрый Π³ΡƒΠ³Π» Ρ€Π΅ΡˆΠ΅Π½ΠΈΠΉ Π½Π΅ подсказал, Π° ΠΈΡΠΊΠ°Ρ‚ΡŒ Π±ΠΎΠ»Π΅Π΅ ΡƒΠ³Π»ΡƒΠ±Π»Π΅Π½Π½ΠΎ Π½Π΅ Π±Ρ‹Π»ΠΎ особого ТСлания. Π’ любом случаС β€” нСплохая Ρ‚Ρ€Π΅Π½ΠΈΡ€ΠΎΠ²ΠΊΠ°.

Ѐормализация Π·Π°Π΄Π°Ρ‡ΠΈ

ΠšΠΎΠ½Π΅Ρ‡Π½Ρ‹ΠΉ Π»ΠΎΠ³-Ρ„Π°ΠΉΠ» прСдставляСт собой мноТСство строк ΠΏΠ΅Ρ€Π΅ΠΌΠ΅Π½Π½ΠΎΠΉ Π΄Π»ΠΈΠ½Ρ‹. ГрафичСски, Π»ΠΎΠ³-Ρ„Π°ΠΉΠ» ΠΌΠΎΠΆΠ½ΠΎ ΠΏΡ€Π΅Π΄ΡΡ‚Π°Π²ΠΈΡ‚ΡŒ, ΠΏΡ€ΠΈΠΌΠ΅Ρ€Π½ΠΎ Ρ‚Π°ΠΊ:
Π’Ρ‹Π³Ρ€ΡƒΠ·ΠΊΠ° Π»ΠΎΠ³Π° PostgreSQL с ΠΎΠ±Π»Π°ΠΊΠ° AWS

Π£ΠΆΠ΅ Ρ‡Ρ‚ΠΎ-Ρ‚ΠΎ Π½Π°ΠΏΠΎΠΌΠΈΠ½Π°Π΅Ρ‚? ΠŸΡ€ΠΈ Ρ‡Ρ‘ΠΌ Ρ‚ΡƒΡ‚ «тСтрис»? А Π²ΠΎΡ‚, ΠΏΡ€ΠΈ Ρ‡Π΅ΠΌ.
Если ΠΏΡ€Π΅Π΄ΡΡ‚Π°Π²ΠΈΡ‚ΡŒ Π²ΠΎΠ·ΠΌΠΎΠΆΠ½Ρ‹Π΅ Π²Π°Ρ€ΠΈΠ°Π½Ρ‚Ρ‹, Π²ΠΎΠ·Π½ΠΈΠΊΠ°ΡŽΡ‰ΠΈΠ΅ ΠΏΡ€ΠΈ Π·Π°Π³Ρ€ΡƒΠ·ΠΊΠ΅ ΠΎΡ‡Π΅Ρ€Π΅Π΄Π½ΠΎΠ³ΠΎ Ρ„Π°ΠΉΠ»Π° графичСски (для простоты, Π² Π΄Π°Π½Π½ΠΎΠΌ случаС, ΠΏΡƒΡΡ‚ΡŒ строки ΠΈΠΌΠ΅ΡŽΡ‚ ΠΎΠ΄Π½Ρƒ Π΄Π»ΠΈΠ½Ρƒ), получатся стандартныС Ρ„ΠΈΠ³ΡƒΡ€Ρ‹ тСтриса:

1) Π€Π°ΠΉΠ» Π·Π°Π³Ρ€ΡƒΠΆΠ΅Π½ Ρ†Π΅Π»ΠΈΠΊΠΎΠΌ ΠΈ являСтся ΠΊΠΎΠ½Π΅Ρ‡Π½Ρ‹ΠΌ. Π Π°Π·ΠΌΠ΅Ρ€ ΠΏΠΎΡ€Ρ†ΠΈΠΈ большС Ρ€Π°Π·ΠΌΠ΅Ρ€Π° ΠΊΠΎΠ½Π΅Ρ‡Π½ΠΎΠ³ΠΎ Ρ„Π°ΠΉΠ»Π°:
Π’Ρ‹Π³Ρ€ΡƒΠ·ΠΊΠ° Π»ΠΎΠ³Π° PostgreSQL с ΠΎΠ±Π»Π°ΠΊΠ° AWS

2) Π€Π°ΠΉΠ» ΠΈΠΌΠ΅Π΅Ρ‚ ΠΏΡ€ΠΎΠ΄ΠΎΠ»ΠΆΠ΅Π½ΠΈΠ΅. Π Π°Π·ΠΌΠ΅Ρ€ ΠΏΠΎΡ€Ρ†ΠΈΠΈ мСньшС Ρ€Π°Π·ΠΌΠ΅Ρ€Π° ΠΊΠΎΠ½Π΅Ρ‡Π½ΠΎΠ³ΠΎ Ρ„Π°ΠΉΠ»Π°:
Π’Ρ‹Π³Ρ€ΡƒΠ·ΠΊΠ° Π»ΠΎΠ³Π° PostgreSQL с ΠΎΠ±Π»Π°ΠΊΠ° AWS

3) Π€Π°ΠΉΠ» являСтся ΠΏΡ€ΠΎΠ΄ΠΎΠ»ΠΆΠ΅Π½ΠΈΠ΅ΠΌ ΠΏΡ€Π΅Π΄Ρ‹Π΄ΡƒΡ‰Π΅Π³ΠΎ Ρ„Π°ΠΉΠ»Π° ΠΈ ΠΈΠΌΠ΅Π΅Ρ‚ ΠΏΡ€ΠΎΠ΄ΠΎΠ»ΠΆΠ΅Π½ΠΈΠ΅. Π Π°Π·ΠΌΠ΅Ρ€ ΠΏΠΎΡ€Ρ†ΠΈΠΈ мСньшС Ρ€Π°Π·ΠΌΠ΅Ρ€Π° остатка ΠΊΠΎΠ½Π΅Ρ‡Π½ΠΎΠ³ΠΎ Ρ„Π°ΠΉΠ»Π°:
Π’Ρ‹Π³Ρ€ΡƒΠ·ΠΊΠ° Π»ΠΎΠ³Π° PostgreSQL с ΠΎΠ±Π»Π°ΠΊΠ° AWS

4) Π€Π°ΠΉΠ» являСтся ΠΏΡ€ΠΎΠ΄ΠΎΠ»ΠΆΠ΅Π½ΠΈΠ΅ΠΌ ΠΏΡ€Π΅Π΄Ρ‹Π΄ΡƒΡ‰Π΅Π³ΠΎ Ρ„Π°ΠΉΠ»Π° ΠΈ являСтся ΠΊΠΎΠ½Π΅Ρ‡Π½Ρ‹ΠΌ. Π Π°Π·ΠΌΠ΅Ρ€ ΠΏΠΎΡ€Ρ†ΠΈΠΈ большС Ρ€Π°Π·ΠΌΠ΅Ρ€Π° остатка ΠΊΠΎΠ½Π΅Ρ‡Π½ΠΎΠ³ΠΎ Ρ„Π°ΠΉΠ»Π°:
Π’Ρ‹Π³Ρ€ΡƒΠ·ΠΊΠ° Π»ΠΎΠ³Π° PostgreSQL с ΠΎΠ±Π»Π°ΠΊΠ° AWS

Π—Π°Π΄Π°Ρ‡Π° β€” ΡΠΎΠ±Ρ€Π°Ρ‚ΡŒ ΠΏΡ€ΡΠΌΠΎΡƒΠ³ΠΎΠ»ΡŒΠ½ΠΈΠΊ ΠΈΠ»ΠΈ ΠΏΠΎΠΈΠ³Ρ€Π°Ρ‚ΡŒ Π² тСтрис, Π½Π° Π½ΠΎΠ²ΠΎΠΌ ΡƒΡ€ΠΎΠ²Π½Π΅.
Π’Ρ‹Π³Ρ€ΡƒΠ·ΠΊΠ° Π»ΠΎΠ³Π° PostgreSQL с ΠΎΠ±Π»Π°ΠΊΠ° AWS

ΠŸΡ€ΠΎΠ±Π»Π΅ΠΌΡ‹, Π²ΠΎΠ·Π½ΠΈΠΊΠ°ΡŽΡ‰ΠΈΠ΅ ΠΏΠΎ Ρ…ΠΎΠ΄Ρƒ Ρ€Π΅ΡˆΠ΅Π½ΠΈΡ Π·Π°Π΄Π°Ρ‡ΠΈ

1) Π‘ΠΊΠ»Π΅ΠΈΡ‚ΡŒ строку ΠΈΠ· 2-Ρ… ΠΏΠΎΡ€Ρ†ΠΈΠΉ

Π’Ρ‹Π³Ρ€ΡƒΠ·ΠΊΠ° Π»ΠΎΠ³Π° PostgreSQL с ΠΎΠ±Π»Π°ΠΊΠ° AWS
Π’ ΠΎΠ±Ρ‰Π΅ΠΌ-Ρ‚ΠΎ Π½ΠΈΠΊΠ°ΠΊΠΈΡ… особых ΠΏΡ€ΠΎΠ±Π»Π΅ΠΌ Π½Π΅ Π²ΠΎΠ·Π½ΠΈΠΊΠ»ΠΎ. Бтандартная Π·Π°Π΄Π°Ρ‡Π° ΠΈΠ· Π½Π°Ρ‡Π°Π»ΡŒΠ½ΠΎΠ³ΠΎ курса программирования.

ΠžΠΏΡ‚ΠΈΠΌΠ°Π»ΡŒΠ½Ρ‹ΠΉ Ρ€Π°Π·ΠΌΠ΅Ρ€ ΠΏΠΎΡ€Ρ†ΠΈΠΈ

А Π²ΠΎΡ‚ это, нСсколько интСрСснСС.
К соТалСнию, Π½Π΅Ρ‚ возмоТности ΠΈΡΠΏΠΎΠ»ΡŒΠ·ΠΎΠ²Π°Ρ‚ΡŒ смСщСниС послС ΠΌΠ΅Ρ‚ΠΊΠΈ Π½Π°Ρ‡Π°Π»ΡŒΠ½ΠΎΠΉ ΠΏΠΎΡ€Ρ†ΠΈΠΈ:

As you already know the option —starting-token is used to specify where to start paginating. This option takes String values which would mean that if you try to add an offset value in front of the Next Token string, the option will not be taken into consideration as an offset.

И поэтому, приходится Ρ‡ΠΈΡ‚Π°Ρ‚ΡŒ кусками-порциями.
Если Ρ‡ΠΈΡ‚Π°Ρ‚ΡŒ большими порциями, Ρ‚ΠΎ количСство Ρ‡Ρ‚Π΅Π½ΠΈΠΉ Π±ΡƒΠ΄Π΅Ρ‚ ΠΌΠΈΠ½ΠΈΠΌΠ°Π»ΡŒΠ½Ρ‹ΠΌ, Π½ΠΎ объСм Π±ΡƒΠ΄Π΅Ρ‚ ΠΌΠ°ΠΊΡΠΈΠΌΠ°Π»ΡŒΠ½Ρ‹ΠΌ.
Если Ρ‡ΠΈΡ‚Π°Ρ‚ΡŒ малСнькими порциями, Ρ‚ΠΎ Π½Π°ΠΎΠ±ΠΎΡ€ΠΎΡ‚, количСство Ρ‡Ρ‚Π΅Π½ΠΈΠΉ Π±ΡƒΠ΄Π΅Ρ‚ ΠΌΠ°ΠΊΡΠΈΠΌΠ°Π»ΡŒΠ½Ρ‹ΠΌ, Π½ΠΎ Π·Π°Ρ‚ΠΎ объСм Π±ΡƒΠ΄Π΅Ρ‚ ΠΌΠΈΠ½ΠΈΠΌΠ°Π»ΡŒΠ½Ρ‹ΠΌ.
ΠŸΠΎΡΡ‚ΠΎΠΌΡƒ, для сокращСния Ρ‚Ρ€Π°Ρ„Ρ„ΠΈΠΊΠ° ΠΈ для ΠΎΠ±Ρ‰Π΅ΠΉ красоты Ρ€Π΅ΡˆΠ΅Π½ΠΈΡ, ΠΏΡ€ΠΈΡˆΠ»ΠΎΡΡŒ ΠΏΡ€ΠΈΠ΄ΡƒΠΌΠ°Ρ‚ΡŒ Π½Π΅ΠΊΠΎΠ΅ Ρ€Π΅ΡˆΠ΅Π½ΠΈΠ΅, ΠΊ соТалСнию, Π½Π΅ΠΌΠ½ΠΎΠ³ΠΎ ΡΠΌΠ°Ρ…ΠΈΠ²Π°ΡŽΡ‰Π΅Π΅ Π½Π° ΠΊΠΎΡΡ‚Ρ‹Π»ΡŒ.

Для ΠΈΠ»Π»ΡŽΡΡ‚Ρ€Π°Ρ†ΠΈΠΈ, рассмотрим процСсс Π·Π°Π³Ρ€ΡƒΠ·ΠΊΠΈ Π»ΠΎΠ³-Ρ„Π°ΠΉΠ»Π° Π² 2-Ρ… сильно ΡƒΠΏΡ€ΠΎΡ‰Π΅Π½Π½Ρ‹Ρ… Π²Π°Ρ€ΠΈΠ°Π½Ρ‚Π°Ρ…. ΠšΠΎΠ»ΠΈΡ‡Π΅ΡΡ‚Π²ΠΎ Ρ‡Ρ‚Π΅Π½ΠΈΠΉ Π² ΠΎΠ±ΠΎΠΈΡ… случаях зависит ΠΎΡ‚ Ρ€Π°Π·ΠΌΠ΅Ρ€Π° ΠΏΠΎΡ€Ρ†ΠΈΠΈ.

1) Π—Π°Π³Ρ€ΡƒΠΆΠ°Π΅ΠΌ ΠΌΠ°Π»Ρ‹ΠΌΠΈ порциями:
Π’Ρ‹Π³Ρ€ΡƒΠ·ΠΊΠ° Π»ΠΎΠ³Π° PostgreSQL с ΠΎΠ±Π»Π°ΠΊΠ° AWS

2) Π—Π°Π³Ρ€ΡƒΠΆΠ°Π΅ΠΌ большими порциями:
Π’Ρ‹Π³Ρ€ΡƒΠ·ΠΊΠ° Π»ΠΎΠ³Π° PostgreSQL с ΠΎΠ±Π»Π°ΠΊΠ° AWS

Как ΠΎΠ±Ρ‹Ρ‡Π½ΠΎ, ΠΎΠΏΡ‚ΠΈΠΌΠ°Π»ΡŒΠ½ΠΎΠ΅ Ρ€Π΅ΡˆΠ΅Π½ΠΈΠ΅-посрСдинС.
Π Π°Π·ΠΌΠ΅Ρ€ ΠΏΠΎΡ€Ρ†ΠΈΠΈ ΠΌΠΈΠ½ΠΈΠΌΠ°Π»ΡŒΠ½Ρ‹ΠΉ, Π½ΠΎ Π² процСссС чтСния, Ρ€Π°Π·ΠΌΠ΅Ρ€ ΠΌΠΎΠΆΠ½ΠΎ ΡƒΠ²Π΅Π»ΠΈΡ‡ΠΈΠ²Π°Ρ‚ΡŒ, для сокращСния числа Ρ‡Ρ‚Π΅Π½ΠΈΠΉ.

НуТно ΠΎΡ‚ΠΌΠ΅Ρ‚ΠΈΡ‚ΡŒ, Ρ‡Ρ‚ΠΎ ΠΏΠΎΠ»Π½ΠΎΡΡ‚ΡŒΡŽ Π·Π°Π΄Π°Ρ‡Π° ΠΏΠΎΠ΄Π±ΠΎΡ€Π° ΠΎΠΏΡ‚ΠΈΠΌΠ°Π»ΡŒΠ½ΠΎΠ³ΠΎ Ρ€Π°Π·ΠΌΠ΅Ρ€Π° считываСмой ΠΏΠΎΡ€Ρ†ΠΈΠΈ ΠΏΠΎΠΊΠ° Π½Π΅ Ρ€Π΅ΡˆΠ΅Π½Π° ΠΈ Ρ‚Ρ€Π΅Π±ΡƒΠ΅Ρ‚ Π±ΠΎΠ»Π΅Π΅ Π³Π»ΡƒΠ±ΠΎΠΊΠΎΠΉ ΠΏΡ€ΠΎΡ€Π°Π±ΠΎΡ‚ΠΊΠΈ ΠΈ Π°Π½Π°Π»ΠΈΠ·Π°. ΠœΠΎΠΆΠ΅Ρ‚, Π±Ρ‹Ρ‚ΡŒ, Ρ‡ΡƒΡ‚ΡŒ ΠΏΠΎΠ·ΠΆΠ΅.

ΠžΠ±Ρ‰Π΅Π΅ описаниС Ρ€Π΅Π°Π»ΠΈΠ·Π°Ρ†ΠΈΠΈ

Π˜ΡΠΏΠΎΠ»ΡŒΠ·ΡƒΠ΅ΠΌΡ‹Π΅ сСрвисныС Ρ‚Π°Π±Π»ΠΈΡ†Ρ‹

CREATE TABLE endpoint
(
id SERIAL ,
host text 
);

TABLE database
(
id SERIAL , 
…
last_aws_log_time text ,
last_aws_nexttoken text ,
aws_max_item_size integer 
);
last_aws_log_time β€” врСмСнная ΠΌΠ΅Ρ‚ΠΊΠ° послСднСго Π·Π°Π³Ρ€ΡƒΠΆΠ΅Π½Π½ΠΎΠ³ΠΎ Π»ΠΎΠ³-Ρ„Π°ΠΉΠ»Π° Π² Ρ„ΠΎΡ€ΠΌΠ°Ρ‚Π΅ YYYY-MM-DD-HH24.
last_aws_nexttoken β€” тСкстовая ΠΌΠ΅Ρ‚ΠΊΠ° послСднСй Π·Π°Π³Ρ€ΡƒΠΆΠ΅Π½Π½ΠΎΠΉ ΠΏΠΎΡ€Ρ†ΠΈΠΈ.
aws_max_item_size- эмпиричСским ΠΏΡƒΡ‚Π΅ΠΌ, ΠΏΠΎΠ΄ΠΎΠ±Ρ€Π°Π½Π½Ρ‹ΠΉ Π½Π°Ρ‡Π°Π»ΡŒΠ½Ρ‹ΠΉ Ρ€Π°Π·ΠΌΠ΅Ρ€ ΠΏΠΎΡ€Ρ†ΠΈΠΈ.

ΠŸΠΎΠ»Π½Ρ‹ΠΉ тСкст скрипта

download_aws_piece.sh

#!/bin/bash
#########################################################
# download_aws_piece.sh
# downloan piece of log from AWS
# version HABR
 let min_item_size=1024
 let max_item_size=1048576
 let growth_factor=3
 let growth_counter=1
 let growth_counter_max=3

 echo $(date +%Y%m%d%H%M)':    download_aws_piece.sh:''STARTED'
 
 AWS_LOG_TIME=$1
 echo $(date +%Y%m%d%H%M)':    download_aws_piece.sh:AWS_LOG_TIME='$AWS_LOG_TIME
  
 database_id=$2
 echo $(date +%Y%m%d%H%M)':    download_aws_piece.sh:database_id='$database_id
 RESULT_FILE=$3 
  
 endpoint=`psql -h MONITOR_ENDPOINT.rds.amazonaws.com -U USER -d MONITOR_DATABASE_DATABASE -A -t -c "select e.host from endpoint e join database d on e.id = d.endpoint_id where d.id = $database_id "`
 echo $(date +%Y%m%d%H%M)':    download_aws_piece.sh:endpoint='$endpoint
  
 db_instance=`echo $endpoint | awk -F"." '{print toupper($1)}'`
 
 echo $(date +%Y%m%d%H%M)':    download_aws_piece.sh:db_instance='$db_instance

 LOG_FILE=$RESULT_FILE'.tmp_log'
 TMP_FILE=$LOG_FILE'.tmp'
 TMP_MIDDLE=$LOG_FILE'.tmp_mid'  
 TMP_MIDDLE2=$LOG_FILE'.tmp_mid2'  
  
 current_aws_log_time=`psql -h MONITOR_ENDPOINT.rds.amazonaws.com -U USER -d MONITOR_DATABASE -A -t -c "select last_aws_log_time from database where id = $database_id "`

 echo $(date +%Y%m%d%H%M)':      download_aws_piece.sh:current_aws_log_time='$current_aws_log_time
  
  if [[ $current_aws_log_time != $AWS_LOG_TIME  ]];
  then
    is_new_log='1'
	if ! psql -h MONITOR_ENDPOINT.rds.amazonaws.com -U USER -d MONITOR_DATABASE -v ON_ERROR_STOP=1 -A -t -q -c "update database set last_aws_log_time = '$AWS_LOG_TIME' where id = $database_id "
	then
	  echo $(date +%Y%m%d%H%M)':    download_aws_piece.sh: FATAL_ERROR - update database set last_aws_log_time .'
	  exit 1
	fi
  else
    is_new_log='0'
  fi
  
  echo $(date +%Y%m%d%H%M)':    download_aws_piece.sh:is_new_log='$is_new_log
  
  let last_aws_max_item_size=`psql -h MONITOR_ENDPOINT.rds.amazonaws.com -U USER -d MONITOR_DATABASE -A -t -c "select aws_max_item_size from database where id = $database_id "`
  echo $(date +%Y%m%d%H%M)':    download_aws_piece.sh: last_aws_max_item_size='$last_aws_max_item_size
  
  let count=1
  if [[ $is_new_log == '1' ]];
  then    
	echo $(date +%Y%m%d%H%M)':    download_aws_piece.sh: START DOWNLOADING OF NEW AWS LOG'
	if ! aws rds download-db-log-file-portion 
		--max-items $last_aws_max_item_size 
		--region REGION 
		--db-instance-identifier  $db_instance 
		--log-file-name error/postgresql.log.$AWS_LOG_TIME > $LOG_FILE
	then
		echo $(date +%Y%m%d%H%M)':    download_aws_piece.sh: FATAL_ERROR - Could not get log from AWS .'
		exit 2
	fi  	
  else
    next_token=`psql -h MONITOR_ENDPOINT.rds.amazonaws.com -U USER -d MONITOR_DATABASE -v ON_ERROR_STOP=1 -A -t -c "select last_aws_nexttoken from database where id = $database_id "`
	
	if [[ $next_token == '' ]];
	then
	  next_token='0'	  
	fi
	
	echo $(date +%Y%m%d%H%M)':    download_aws_piece.sh: CONTINUE DOWNLOADING OF AWS LOG'
	if ! aws rds download-db-log-file-portion 
	    --max-items $last_aws_max_item_size 
		--starting-token $next_token 
		--region REGION 
		--db-instance-identifier  $db_instance 
		--log-file-name error/postgresql.log.$AWS_LOG_TIME > $LOG_FILE
	then
		echo $(date +%Y%m%d%H%M)':    download_aws_piece.sh: FATAL_ERROR - Could not get log from AWS .'
		exit 3
	fi       
	
	line_count=`cat  $LOG_FILE | wc -l`
	let lines=$line_count-1
	  
	tail -$lines $LOG_FILE > $TMP_MIDDLE 
	mv -f $TMP_MIDDLE $LOG_FILE
  fi
  
  next_token_str=`cat $LOG_FILE | grep NEXTTOKEN` 
  next_token=`echo $next_token_str | awk -F" " '{ print $2}' `
  
  grep -v NEXTTOKEN $LOG_FILE  > $TMP_FILE 
  
  if [[ $next_token == '' ]];
  then
	  cp $TMP_FILE $RESULT_FILE
	  
	  echo $(date +%Y%m%d%H%M)':    download_aws_piece.sh:  NEXTTOKEN NOT FOUND - FINISH '
	  rm $LOG_FILE 
	  rm $TMP_FILE
	  rm $TMP_MIDDLE
          rm $TMP_MIDDLE2	  
	  exit 0  
  else
	psql -h MONITOR_ENDPOINT.rds.amazonaws.com -U USER -d MONITOR_DATABASE -v ON_ERROR_STOP=1 -A -t -q -c "update database set last_aws_nexttoken = '$next_token' where id = $database_id "
  fi
  
  first_str=`tail -1 $TMP_FILE`
  
  line_count=`cat  $TMP_FILE | wc -l`
  let lines=$line_count-1    
  
  head -$lines $TMP_FILE  > $RESULT_FILE

###############################################
# MAIN CIRCLE
  let count=2
  while [[ $next_token != '' ]];
  do 
    echo $(date +%Y%m%d%H%M)':    download_aws_piece.sh: count='$count
	
	echo $(date +%Y%m%d%H%M)':    download_aws_piece.sh: START DOWNLOADING OF AWS LOG'
	if ! aws rds download-db-log-file-portion 
             --max-items $last_aws_max_item_size 
             --starting-token $next_token 
             --region REGION 
             --db-instance-identifier  $db_instance 
             --log-file-name error/postgresql.log.$AWS_LOG_TIME > $LOG_FILE
	then
		echo $(date +%Y%m%d%H%M)':    download_aws_piece.sh: FATAL_ERROR - Could not get log from AWS .'
		exit 4
	fi

	next_token_str=`cat $LOG_FILE | grep NEXTTOKEN` 
	next_token=`echo $next_token_str | awk -F" " '{ print $2}' `

	TMP_FILE=$LOG_FILE'.tmp'
	grep -v NEXTTOKEN $LOG_FILE  > $TMP_FILE  
	
	last_str=`head -1 $TMP_FILE`
  
    if [[ $next_token == '' ]];
	then
	  concat_str=$first_str$last_str
	  	  
	  echo $concat_str >> $RESULT_FILE
		 
	  line_count=`cat  $TMP_FILE | wc -l`
	  let lines=$line_count-1
	  
	  tail -$lines $TMP_FILE >> $RESULT_FILE
	  
	  echo $(date +%Y%m%d%H%M)':    download_aws_piece.sh:  NEXTTOKEN NOT FOUND - FINISH '
	  rm $LOG_FILE 
	  rm $TMP_FILE
	  rm $TMP_MIDDLE
          rm $TMP_MIDDLE2	  
	  exit 0  
	fi
	
    if [[ $next_token != '' ]];
	then
		let growth_counter=$growth_counter+1
		if [[ $growth_counter -gt $growth_counter_max ]];
		then
			let last_aws_max_item_size=$last_aws_max_item_size*$growth_factor
			let growth_counter=1
		fi
	
		if [[ $last_aws_max_item_size -gt $max_item_size ]]; 
		then
			let last_aws_max_item_size=$max_item_size
		fi 

	  psql -h MONITOR_ENDPOINT.rds.amazonaws.com -U USER -d MONITOR_DATABASE -A -t -q -c "update database set last_aws_nexttoken = '$next_token' where id = $database_id "
	  
	  concat_str=$first_str$last_str
	  	  
	  echo $concat_str >> $RESULT_FILE
		 
	  line_count=`cat  $TMP_FILE | wc -l`
	  let lines=$line_count-1
	  
	  #############################
	  #Get middle of file
	  head -$lines $TMP_FILE > $TMP_MIDDLE
	  
	  line_count=`cat  $TMP_MIDDLE | wc -l`
	  let lines=$line_count-1
	  tail -$lines $TMP_MIDDLE > $TMP_MIDDLE2
	  
	  cat $TMP_MIDDLE2 >> $RESULT_FILE	  
	  
	  first_str=`tail -1 $TMP_FILE`	  
	fi
	  
    let count=$count+1

  done
#
#################################################################

exit 0  

Π€Ρ€Π°Π³ΠΌΠ΅Π½Ρ‚Ρ‹ скрипта с Π½Π΅ΠΊΠΎΡ‚ΠΎΡ€Ρ‹ΠΌΠΈ пояснСниями:

Π’Ρ…ΠΎΠ΄Π½Ρ‹Π΅ ΠΏΠ°Ρ€Π°ΠΌΠ΅Ρ‚Ρ€Ρ‹ скрипта:

  • ВрСмСнная ΠΌΠ΅Ρ‚ΠΊΠ° ΠΈΠΌΠ΅Π½ΠΈ Π»ΠΎΠ³-Ρ„Π°ΠΉΠ»Π° Π² Ρ„ΠΎΡ€ΠΌΠ°Ρ‚Π΅ YYYY-MM-DD-HH24: AWS_LOG_TIME=$1
  • ID Π‘Π°Π·Ρ‹ Π΄Π°Π½Π½Ρ‹Ρ…: database_id=$2
  • Имя собранного Π»ΠΎΠ³-Ρ„Π°ΠΉΠ»Π°: RESULT_FILE=$3

ΠŸΠΎΠ»ΡƒΡ‡ΠΈΡ‚ΡŒ Π²Ρ€Π΅ΠΌΠ΅Π½Π½ΡƒΡŽ ΠΌΠ΅Ρ‚ΠΊΡƒ послСднСго Π·Π°Π³Ρ€ΡƒΠΆΠ΅Π½Π½ΠΎΠ³ΠΎ Π»ΠΎΠ³-Ρ„Π°ΠΉΠ»Π°:

current_aws_log_time=`psql -h MONITOR_ENDPOINT.rds.amazonaws.com -U USER -d MONITOR_DATABASE -A -t -c "select last_aws_log_time from database where id = $database_id "`

Если врСмСнная ΠΌΠ΅Ρ‚ΠΊΠ° послСднСго Π·Π°Π³Ρ€ΡƒΠΆΠ΅Π½Π½ΠΎΠ³ΠΎ Π»ΠΎΠ³-Ρ„Π°ΠΉΠ»Π° Π½Π΅ совпадаСт с Π²Ρ…ΠΎΠ΄Π½Ρ‹ΠΌ ΠΏΠ°Ρ€Π°ΠΌΠ΅Ρ‚Ρ€ΠΎΠΌ β€” загруТаСтся Π½ΠΎΠ²Ρ‹ΠΉ Π»ΠΎΠ³-Ρ„Π°ΠΉΠ»:

if [[ $current_aws_log_time != $AWS_LOG_TIME  ]];
  then
    is_new_log='1'
	if ! psql -h ENDPOINT.rds.amazonaws.com -U USER -d MONITOR_DATABASE -v ON_ERROR_STOP=1 -A -t -c "update database set last_aws_log_time = '$AWS_LOG_TIME' where id = $database_id "
	then
	  echo '***download_aws_piece.sh -FATAL_ERROR - update database set last_aws_log_time .'
	  exit 1
	fi
  else
    is_new_log='0'
  fi

ΠŸΠΎΠ»ΡƒΡ‡Π°Π΅ΠΌ Π·Π½Π°Ρ‡Π΅Π½ΠΈΠ΅ ΠΌΠ΅Ρ‚ΠΊΠΈ nexttoken ΠΈΠ· Π·Π°Π³Ρ€ΡƒΠΆΠ΅Π½Π½ΠΎΠ³ΠΎ Ρ„Π°ΠΉΠ»Π°:

  next_token_str=`cat $LOG_FILE | grep NEXTTOKEN` 
  next_token=`echo $next_token_str | awk -F" " '{ print $2}' `

ΠŸΡ€ΠΈΠ·Π½Π°ΠΊΠΎΠΌ окончания Π·Π°Π³Ρ€ΡƒΠ·ΠΊΠΈ слуТит пустоС Π·Π½Π°Ρ‡Π΅Π½ΠΈΠ΅ nexttoken.

Π’ Ρ†ΠΈΠΊΠ»Π΅ считаСм ΠΏΠΎΡ€Ρ†ΠΈΠΈ Ρ„Π°ΠΉΠ»Π°, ΠΏΠΎΠΏΡƒΡ‚Π½ΠΎ, сцСпляя строки ΠΈ увСличивая Ρ€Π°Π·ΠΌΠ΅Ρ€ ΠΏΠΎΡ€Ρ†ΠΈΠΈ:
Π“Π»Π°Π²Π½Ρ‹ΠΉ Ρ†ΠΈΠΊΠ»

# MAIN CIRCLE
  let count=2
  while [[ $next_token != '' ]];
  do 
    echo $(date +%Y%m%d%H%M)':    download_aws_piece.sh: count='$count
	
	echo $(date +%Y%m%d%H%M)':    download_aws_piece.sh: START DOWNLOADING OF AWS LOG'
	if ! aws rds download-db-log-file-portion 
     --max-items $last_aws_max_item_size 
	 --starting-token $next_token 
     --region REGION 
     --db-instance-identifier  $db_instance 
     --log-file-name error/postgresql.log.$AWS_LOG_TIME > $LOG_FILE
	then
		echo $(date +%Y%m%d%H%M)':    download_aws_piece.sh: FATAL_ERROR - Could not get log from AWS .'
		exit 4
	fi

	next_token_str=`cat $LOG_FILE | grep NEXTTOKEN` 
	next_token=`echo $next_token_str | awk -F" " '{ print $2}' `

	TMP_FILE=$LOG_FILE'.tmp'
	grep -v NEXTTOKEN $LOG_FILE  > $TMP_FILE  
	
	last_str=`head -1 $TMP_FILE`
  
    if [[ $next_token == '' ]];
	then
	  concat_str=$first_str$last_str
	  	  
	  echo $concat_str >> $RESULT_FILE
		 
	  line_count=`cat  $TMP_FILE | wc -l`
	  let lines=$line_count-1
	  
	  tail -$lines $TMP_FILE >> $RESULT_FILE
	  
	  echo $(date +%Y%m%d%H%M)':    download_aws_piece.sh:  NEXTTOKEN NOT FOUND - FINISH '
	  rm $LOG_FILE 
	  rm $TMP_FILE
	  rm $TMP_MIDDLE
         rm $TMP_MIDDLE2	  
	  exit 0  
	fi
	
    if [[ $next_token != '' ]];
	then
		let growth_counter=$growth_counter+1
		if [[ $growth_counter -gt $growth_counter_max ]];
		then
			let last_aws_max_item_size=$last_aws_max_item_size*$growth_factor
			let growth_counter=1
		fi
	
		if [[ $last_aws_max_item_size -gt $max_item_size ]]; 
		then
			let last_aws_max_item_size=$max_item_size
		fi 

	  psql -h MONITOR_ENDPOINT.rds.amazonaws.com -U USER -d MONITOR_DATABASE -A -t -q -c "update database set last_aws_nexttoken = '$next_token' where id = $database_id "
	  
	  concat_str=$first_str$last_str
	  	  
	  echo $concat_str >> $RESULT_FILE
		 
	  line_count=`cat  $TMP_FILE | wc -l`
	  let lines=$line_count-1
	  
	  #############################
	  #Get middle of file
	  head -$lines $TMP_FILE > $TMP_MIDDLE
	  
	  line_count=`cat  $TMP_MIDDLE | wc -l`
	  let lines=$line_count-1
	  tail -$lines $TMP_MIDDLE > $TMP_MIDDLE2
	  
	  cat $TMP_MIDDLE2 >> $RESULT_FILE	  
	  
	  first_str=`tail -1 $TMP_FILE`	  
	fi
	  
    let count=$count+1

  done

Π§Ρ‚ΠΎ ΠΆΠ΅ дальшС ?

Π˜Ρ‚Π°ΠΊ, пСрвая промСТуточная Π·Π°Π΄Π°Ρ‡Π° β€” Β«Π·Π°Π³Ρ€ΡƒΠ·ΠΈΡ‚ΡŒ Π»ΠΎΠ³-Ρ„Π°ΠΉΠ» с ΠΎΠ±Π»Π°ΠΊΠ°Β» Ρ€Π΅ΡˆΠ΅Π½Π°. Π§Ρ‚ΠΎ Π΄Π΅Π»Π°Ρ‚ΡŒ с Π·Π°Π³Ρ€ΡƒΠΆΠ΅Π½Π½Ρ‹ΠΌ Π»ΠΎΠ³ΠΎΠΌ?
Для Π½Π°Ρ‡Π°Π»Π° Π½Π΅ΠΎΠ±Ρ…ΠΎΠ΄ΠΈΠΌΠΎ Ρ€Π°Π·ΠΎΠ±Ρ€Π°Ρ‚ΡŒ Π»ΠΎΠ³-Ρ„Π°ΠΉΠ» ΠΈ Π²Ρ‹Π΄Π΅Π»ΠΈΡ‚ΡŒ ΠΈΠ· Π½Π΅Π³ΠΎ собствСнно запросы.
Π—Π°Π΄Π°Ρ‡Π° Π½Π΅ сильно слоТная. ΠŸΡ€ΠΎΡΡ‚Π΅ΠΉΡˆΠΈΠΉ bash-script Π²ΠΏΠΎΠ»Π½Π΅ справляСтся.
upload_log_query.sh

#!/bin/bash
#########################################################
# upload_log_query.sh
# Upload table table from dowloaded aws file 
# version HABR
###########################################################  
echo 'TIMESTAMP:'$(date +%c)' Upload log_query table '
source_file=$1
echo 'source_file='$source_file
database_id=$2
echo 'database_id='$database_id

beginer=' '
first_line='1'
let "line_count=0"
sql_line=' '
sql_flag=' '    
space=' '
cat $source_file | while read line
do
  line="$space$line"

  if [[ $first_line == "1" ]]; then
    beginer=`echo $line | awk -F" " '{ print $1}' `
    first_line='0'
  fi

  current_beginer=`echo $line | awk -F" " '{ print $1}' `

  if [[ $current_beginer == $beginer ]]; then
    if [[ $sql_flag == '1' ]]; then
     sql_flag='0' 
     log_date=`echo $sql_line | awk -F" " '{ print $1}' `
     log_time=`echo $sql_line | awk -F" " '{ print $2}' `
     duration=`echo $sql_line | awk -F" " '{ print $5}' `

     #replace ' to ''
     sql_modline=`echo "$sql_line" | sed 's/'''/''''''/g'`
     sql_line=' '

	 ################
	 #PROCESSING OF THE SQL-SELECT IS HERE
     if ! psql -h ENDPOINT.rds.amazonaws.com -U USER -d DATABASE -v ON_ERROR_STOP=1 -A -t -c "select log_query('$ip_port',$database_id , '$log_date' , '$log_time' , '$duration' , '$sql_modline' )" 
     then
        echo 'FATAL_ERROR - log_query '
        exit 1
     fi
	 ################

    fi #if [[ $sql_flag == '1' ]]; then

    let "line_count=line_count+1"

    check=`echo $line | awk -F" " '{ print $8}' `
    check_sql=${check^^}    

    #echo 'check_sql='$check_sql
    
    if [[ $check_sql == 'SELECT' ]]; then
     sql_flag='1'    
     sql_line="$sql_line$line"
	 ip_port=`echo $sql_line | awk -F":" '{ print $4}' `
    fi
  else       

    if [[ $sql_flag == '1' ]]; then
      sql_line="$sql_line$line"
    fi   
    
  fi #if [[ $current_beginer == $beginer ]]; then

done

Π’Π΅ΠΏΠ΅Ρ€ΡŒ с Π²Ρ‹Π΄Π΅Π»Π΅Π½Π½Ρ‹ΠΌ ΠΈΠ· Π»ΠΎΠ³-Ρ„Π°ΠΉΠ»Π° запросом, ΠΌΠΎΠΆΠ½ΠΎ Ρ€Π°Π±ΠΎΡ‚Π°Ρ‚ΡŒ.

А ΠΏΠΎΠ»Π΅Π·Π½Ρ‹Ρ… возмоТностСй открываСтся нСсколько.

Π Π°Π·ΠΎΠ±Ρ€Π°Π½Π½Ρ‹Π΅ запросы Π½Π°Π΄ΠΎ Π³Π΄Π΅-Ρ‚ΠΎ Ρ…Ρ€Π°Π½ΠΈΡ‚ΡŒ. Для этого ΠΈΡΠΏΠΎΠ»ΡŒΠ·ΡƒΠ΅Ρ‚ΡΡ сСрвисная Ρ‚Π°Π±Π»ΠΈΡ†Π° log_query

CREATE TABLE log_query
(
   id SERIAL ,
   queryid bigint ,
   query_md5hash text not null ,
   database_id integer not null ,  
   timepoint timestamp without time zone not null,
   duration double precision not null ,
   query text not null ,
   explained_plan text[],
   plan_md5hash text  , 
   explained_plan_wo_costs text[],
   plan_hash_value text  ,
   baseline_id integer ,
   ip text ,
   port text 
);
ALTER TABLE log_query ADD PRIMARY KEY (id);
ALTER TABLE log_query ADD CONSTRAINT queryid_timepoint_unique_key UNIQUE (queryid, timepoint );
ALTER TABLE log_query ADD CONSTRAINT query_md5hash_timepoint_unique_key UNIQUE (query_md5hash, timepoint );

CREATE INDEX log_query_timepoint_idx ON log_query (timepoint);
CREATE INDEX log_query_queryid_idx ON log_query (queryid);
ALTER TABLE log_query ADD CONSTRAINT database_id_fk FOREIGN KEY (database_id) REFERENCES database (id) ON DELETE CASCADE ;

ΠžΠ±Ρ€Π°Π±ΠΎΡ‚ΠΊΠ° Ρ€Π°Π·ΠΎΠ±Ρ€Π°Π½Π½ΠΎΠ³ΠΎ запроса осущСствляСтся Π² plpgsql Ρ„ΡƒΠ½ΠΊΡ†ΠΈΠΈ Β«log_queryΒ».
log_query.sql

--log_query.sql
--verison HABR
CREATE OR REPLACE FUNCTION log_query( ip_port text ,log_database_id integer , log_date text , log_time text , duration text , sql_line text   ) RETURNS boolean AS $$
DECLARE
  result boolean ;
  log_timepoint timestamp without time zone ;
  log_duration double precision ; 
  pos integer ;
  log_query text ;
  activity_string text ;
  log_md5hash text ;
  log_explain_plan text[] ;
  
  log_planhash text ;
  log_plan_wo_costs text[] ; 
  
  database_rec record ;
  
  pg_stat_query text ; 
  test_log_query text ;
  log_query_rec record;
  found_flag boolean;
  
  pg_stat_history_rec record ;
  port_start integer ;
  port_end integer ;
  client_ip text ;
  client_port text ;
  log_queryid bigint ;
  log_query_text text ;
  pg_stat_query_text text ; 
BEGIN
  result = TRUE ;

  RAISE NOTICE '***log_query';
  
  port_start = position('(' in ip_port);
  port_end = position(')' in ip_port);
  client_ip = substring( ip_port from 1 for port_start-1 );
  client_port = substring( ip_port from port_start+1 for port_end-port_start-1 );

  SELECT e.host , d.name , d.owner_pwd 
  INTO database_rec
  FROM database d JOIN endpoint e ON e.id = d.endpoint_id
  WHERE d.id = log_database_id ;
  
  log_timepoint = to_timestamp(log_date||' '||log_time,'YYYY-MM-DD HH24-MI-SS');
  log_duration = duration:: double precision; 

  
  pos = position ('SELECT' in UPPER(sql_line) );
  log_query = substring( sql_line from pos for LENGTH(sql_line));
  log_query = regexp_replace(log_query,' +',' ','g');
  log_query = regexp_replace(log_query,';+','','g');
  log_query = trim(trailing ' ' from log_query);
 

  log_md5hash = md5( log_query::text );
  
  --Explain execution plan--
  EXECUTE 'SELECT dblink_connect(''LINK1'',''host='||database_rec.host||' dbname='||database_rec.name||' user=DATABASE password='||database_rec.owner_pwd||' '')'; 
  
  log_explain_plan = ARRAY ( SELECT * FROM dblink('LINK1', 'EXPLAIN '||log_query ) AS t (plan text) );
  log_plan_wo_costs = ARRAY ( SELECT * FROM dblink('LINK1', 'EXPLAIN ( COSTS FALSE ) '||log_query ) AS t (plan text) );
    
  PERFORM dblink_disconnect('LINK1');
  --------------------------
  BEGIN
	INSERT INTO log_query
	(
		query_md5hash ,
		database_id , 
		timepoint ,
		duration ,
		query ,
		explained_plan ,
		plan_md5hash , 
		explained_plan_wo_costs , 
		plan_hash_value , 
		ip , 
		port
	) 
	VALUES 
	(
		log_md5hash ,
		log_database_id , 
		log_timepoint , 
		log_duration , 
		log_query ,
		log_explain_plan , 
		md5(log_explain_plan::text) ,
		log_plan_wo_costs , 
		md5(log_plan_wo_costs::text),
		client_ip , 
		client_port		
	);
	activity_string = 	'New query has logged '||
						' database_id = '|| log_database_id ||
						' query_md5hash='||log_md5hash||
						' , timepoint = '||to_char(log_timepoint,'YYYYMMDD HH24:MI:SS');
					
	RAISE NOTICE '%',activity_string;					
					 
	PERFORM pg_log( log_database_id , 'log_query' , activity_string);  

	EXCEPTION
	  WHEN unique_violation THEN
		RAISE NOTICE '*** unique_violation *** query already has logged';
	END;

	SELECT 	queryid
	INTO   	log_queryid
	FROM 	log_query 
	WHERE 	query_md5hash = log_md5hash AND
			timepoint = log_timepoint;

	IF log_queryid IS NOT NULL 
	THEN 
	  RAISE NOTICE 'log_query with query_md5hash = % and timepoint = % has already has a QUERYID = %',log_md5hash,log_timepoint , log_queryid ;
	  RETURN result;
	END IF;
	
	------------------------------------------------
	RAISE NOTICE 'Update queryid';	
	
	SELECT * 
	INTO log_query_rec
	FROM log_query
	WHERE query_md5hash = log_md5hash AND timepoint = log_timepoint ; 
	
	log_query_rec.query=regexp_replace(log_query_rec.query,';+','','g');
	
	FOR pg_stat_history_rec IN
	 SELECT 
         queryid ,
	  query 
	 FROM 
         pg_stat_db_queries 
     WHERE  
      database_id = log_database_id AND
       queryid is not null 
	LOOP
	  pg_stat_query = pg_stat_history_rec.query ; 
	  pg_stat_query=regexp_replace(pg_stat_query,'n+',' ','g');
	  pg_stat_query=regexp_replace(pg_stat_query,'t+',' ','g');
	  pg_stat_query=regexp_replace(pg_stat_query,' +',' ','g');
	  pg_stat_query=regexp_replace(pg_stat_query,'$.','%','g');
	
	  log_query_text = trim(trailing ' ' from log_query_rec.query);
	  pg_stat_query_text = pg_stat_query; 
	
	  
	  --SELECT log_query_rec.query like pg_stat_query INTO found_flag ; 
	  IF (log_query_text LIKE pg_stat_query_text) THEN
		found_flag = TRUE ;
	  ELSE
		found_flag = FALSE ;
	  END IF;	  
	  
	  
	  IF found_flag THEN
	    
		UPDATE log_query SET queryid = pg_stat_history_rec.queryid WHERE query_md5hash = log_md5hash AND timepoint = log_timepoint ;
		activity_string = 	' updated queryid = '||pg_stat_history_rec.queryid||
		                    ' for log_query with id = '||log_query_rec.id               
		   				    ;						
	    RAISE NOTICE '%',activity_string;	
		EXIT ;
	  END IF ;
	  
	END LOOP ;
	
  RETURN result ;
END
$$ LANGUAGE plpgsql;

ΠŸΡ€ΠΈ ΠΎΠ±Ρ€Π°Π±ΠΎΡ‚ΠΊΠ΅ ΠΈΡΠΏΠΎΠ»ΡŒΠ·ΡƒΠ΅Ρ‚ΡΡ сСрвисная Ρ‚Π°Π±Π»ΠΈΡ†Π° pg_stat_db_queries, содСрТащая снимок Ρ‚Π΅ΠΊΡƒΡ‰ΠΈΡ… запросов ΠΈΠ· Ρ‚Π°Π±Π»ΠΈΡ†Ρ‹ pg_stat_history (ИспользованиС Ρ‚Π°Π±Π»ΠΈΡ†Ρ‹ описано здСсь β€” ΠœΠΎΠ½ΠΈΡ‚ΠΎΡ€ΠΈΠ½Π³ ΠΏΡ€ΠΎΠΈΠ·Π²ΠΎΠ΄ΠΈΡ‚Π΅Π»ΡŒΠ½ΠΎΡΡ‚ΠΈ запросов PostgreSQL. Π§Π°ΡΡ‚ΡŒ 1 β€” Ρ€Π΅ΠΏΠΎΡ€Ρ‚ΠΈΠ½Π³)

TABLE pg_stat_db_queries
(
   database_id integer,  
   queryid bigint ,  
   query text , 
   max_time double precision 
);

TABLE pg_stat_history 
(
…
database_id integer ,
…
queryid bigint ,
…
max_time double precision	 , 	
…
);

Ѐункция позволяСт ΠΎΡΡƒΡ‰Π΅ΡΡ‚Π²ΠΈΡ‚ΡŒ ряд ΠΏΠΎΠ»Π΅Π·Π½Ρ‹Ρ… возмоТностСй ΠΏΠΎ ΠΎΠ±Ρ€Π°Π±ΠΎΡ‚ΠΊΠ΅ запросов ΠΈΠ· Π»ΠΎΠ³-Ρ„Π°ΠΉΠ»Π°. А ΠΈΠΌΠ΅Π½Π½ΠΎ:

Π’ΠΎΠ·ΠΌΠΎΠΆΠ½ΠΎΡΡ‚ΡŒ β„–1 β€” Π˜ΡΡ‚ΠΎΡ€ΠΈΡ выполнСния запросов

ΠžΡ‡Π΅Π½ΡŒ ΠΏΠΎΠ»Π΅Π·Π½ΠΎ для Π½Π°Ρ‡Π°Π»Π° Ρ€Π΅ΡˆΠ΅Π½ΠΈΡ ΠΈΠ½Ρ†ΠΈΠ΄Π΅Π½Ρ‚Π° ΠΏΡ€ΠΎΠΈΠ·Π²ΠΎΠ΄ΠΈΡ‚Π΅Π»ΡŒΠ½ΠΎΡΡ‚ΠΈ. Π‘Π½Π°Ρ‡Π°Π»Π° ΠΎΠ·Π½Π°ΠΊΠΎΠΌΠΈΡ‚ΡŒΡΡ с историСй β€” Π° ΠΊΠΎΠ³Π΄Π° Π½Π°Ρ‡Π°Π»ΠΎΡΡŒ Π·Π°ΠΌΠ΅Π΄Π»Π΅Π½ΠΈΠ΅?
Π—Π°Ρ‚Π΅ΠΌ, ΠΏΠΎ классикС – ΠΏΠΎΠΈΡΠΊΠ°Ρ‚ΡŒ внСшниС ΠΏΡ€ΠΈΡ‡ΠΈΠ½Ρ‹. ΠœΠΎΠΆΠ΅Ρ‚ Π±Ρ‹Ρ‚ΡŒ просто Ρ€Π΅Π·ΠΊΠΎ ΡƒΠ²Π΅Π»ΠΈΡ‡ΠΈΠ»Π°ΡΡŒ Π·Π°Π³Ρ€ΡƒΠ·ΠΊΠ° Π±Π°Π·Ρ‹ Π΄Π°Π½Π½Ρ‹Ρ… ΠΈ ΠΊΠΎΠ½ΠΊΡ€Π΅Ρ‚Π½Ρ‹ΠΉ запрос Π½ΠΈ ΠΏΡ€ΠΈ Ρ‡Π΅ΠΌ.
Π”ΠΎΠ±Π°Π²ΠΈΡ‚ΡŒ Π½ΠΎΠ²ΡƒΡŽ запись Π² Ρ‚Π°Π±Π»ΠΈΡ†Ρƒ log_query

  port_start = position('(' in ip_port);
  port_end = position(')' in ip_port);
  client_ip = substring( ip_port from 1 for port_start-1 );
  client_port = substring( ip_port from port_start+1 for port_end-port_start-1 );

  SELECT e.host , d.name , d.owner_pwd 
  INTO database_rec
  FROM database d JOIN endpoint e ON e.id = d.endpoint_id
  WHERE d.id = log_database_id ;
  
  log_timepoint = to_timestamp(log_date||' '||log_time,'YYYY-MM-DD HH24-MI-SS');
  log_duration = to_number(duration,'99999999999999999999D9999999999'); 

  
  pos = position ('SELECT' in UPPER(sql_line) );
  log_query = substring( sql_line from pos for LENGTH(sql_line));
  log_query = regexp_replace(log_query,' +',' ','g');
  log_query = regexp_replace(log_query,';+','','g');
  log_query = trim(trailing ' ' from log_query);
 
  RAISE NOTICE 'log_query=%',log_query ;   

  log_md5hash = md5( log_query::text );
  
  --Explain execution plan--
  EXECUTE 'SELECT dblink_connect(''LINK1'',''host='||database_rec.host||' dbname='||database_rec.name||' user=DATABASE password='||database_rec.owner_pwd||' '')'; 
  
  log_explain_plan = ARRAY ( SELECT * FROM dblink('LINK1', 'EXPLAIN '||log_query ) AS t (plan text) );
  log_plan_wo_costs = ARRAY ( SELECT * FROM dblink('LINK1', 'EXPLAIN ( COSTS FALSE ) '||log_query ) AS t (plan text) );
    
  PERFORM dblink_disconnect('LINK1');
  --------------------------
  BEGIN
	INSERT INTO log_query
	(
		query_md5hash ,
		database_id , 
		timepoint ,
		duration ,
		query ,
		explained_plan ,
		plan_md5hash , 
		explained_plan_wo_costs , 
		plan_hash_value , 
		ip , 
		port
	) 
	VALUES 
	(
		log_md5hash ,
		log_database_id , 
		log_timepoint , 
		log_duration , 
		log_query ,
		log_explain_plan , 
		md5(log_explain_plan::text) ,
		log_plan_wo_costs , 
		md5(log_plan_wo_costs::text),
		client_ip , 
		client_port		
	);

Π’ΠΎΠ·ΠΌΠΎΠΆΠ½ΠΎΡΡ‚ΡŒ β„–2 β€” Π‘ΠΎΡ…Ρ€Π°Π½ΡΡ‚ΡŒ ΠΏΠ»Π°Π½Ρ‹ выполнСния запросов

На этом мСстС ΠΌΠΎΠΆΠ΅Ρ‚ Π²ΠΎΠ·Π½ΠΈΠΊΠ½ΡƒΡ‚ΡŒ Π²ΠΎΠ·Ρ€Π°ΠΆΠ΅Π½ΠΈΠ΅-ΡƒΡ‚ΠΎΡ‡Π½Π΅Π½ΠΈΠ΅-ΠΊΠΎΠΌΠΌΠ΅Π½Ρ‚Π°Ρ€ΠΈΠΉ: «Но вСдь ΡƒΠΆΠ΅ Π΅ΡΡ‚ΡŒ autoexplainΒ». Π•ΡΡ‚ΡŒ Ρ‚ΠΎ ΠΎΠ½ Π΅ΡΡ‚ΡŒ, Π° Ρ‡Ρ‚ΠΎ Ρ‚ΠΎΠ»ΠΊΡƒ, Ссли ΠΏΠ»Π°Π½ выполнСния хранится Π² Ρ‚ΠΎΠΌ ΠΆΠ΅ Π»ΠΎΠ³-Ρ„Π°ΠΉΠ»Π΅ ΠΈ для Ρ‚ΠΎΠ³ΠΎ, Ρ‡Ρ‚ΠΎΠ±Ρ‹ Π΅Π³ΠΎ ΡΠΎΡ…Ρ€Π°Π½ΠΈΡ‚ΡŒ для дальнСйшСго Π°Π½Π°Π»ΠΈΠ·Π°, придётся ΠΏΠ°Ρ€ΡΠΈΡ‚ΡŒ Π»ΠΎΠ³-Ρ„Π°ΠΉΠ»?

МнС, ΠΆΠ΅, Π½ΡƒΠΆΠ½ΠΎ Π±Ρ‹Π»ΠΎ:
Π²ΠΎ-ΠΏΠ΅Ρ€Π²Ρ‹Ρ…: Ρ…Ρ€Π°Π½ΠΈΡ‚ΡŒ ΠΏΠ»Π°Π½ выполнСния Π² сСрвисной Ρ‚Π°Π±Π»ΠΈΡ†Π΅ Π±Π°Π·Ρ‹ Π΄Π°Π½Π½Ρ‹Ρ… ΠΌΠΎΠ½ΠΈΡ‚ΠΎΡ€ΠΈΠ½Π³Π°;
Π²ΠΎ-Π²Ρ‚ΠΎΡ€Ρ‹Ρ…: ΠΈΠΌΠ΅Ρ‚ΡŒ возмоТности ΡΡ€Π°Π²Π½ΠΈΠ²Π°Ρ‚ΡŒ ΠΏΠ»Π°Π½Ρ‹ выполнСния ΠΌΠ΅ΠΆΠ΄Ρƒ собой, Ρ‡Ρ‚ΠΎ Π±Ρ‹ сразу Π²ΠΈΠ΄Π΅Ρ‚ΡŒ, Ρ‡Ρ‚ΠΎ ΠΏΠ»Π°Π½ выполнСния запроса измСнился.

Запрос с ΠΊΠΎΠ½ΠΊΡ€Π΅Ρ‚Π½Ρ‹ΠΌΠΈ ΠΏΠ°Ρ€Π°ΠΌΠ΅Ρ‚Ρ€Π°ΠΌΠΈ выполнСния имССтся. ΠŸΠΎΠ»ΡƒΡ‡ΠΈΡ‚ΡŒ ΠΈ ΡΠΎΡ…Ρ€Π°Π½ΠΈΡ‚ΡŒ Π΅Π³ΠΎ ΠΏΠ»Π°Π½ выполнСния, ΠΈΡΠΏΠΎΠ»ΡŒΠ·ΡƒΡ EXPLAIN β€” Π·Π°Π΄Π°Ρ‡Π° элСмСнтарная.
Π‘ΠΎΠ»Π΅Π΅ Ρ‚ΠΎΠ³ΠΎ, ΠΈΡΠΏΠΎΠ»ΡŒΠ·ΡƒΡ Π²Ρ‹Ρ€Π°ΠΆΠ΅Π½ΠΈΠ΅ EXPLAIN (COSTS FALSE), ΠΌΠΎΠΆΠ½ΠΎ ΠΏΠΎΠ»ΡƒΡ‡ΠΈΡ‚ΡŒ каркас ΠΏΠ»Π°Π½Π°, ΠΊΠΎΡ‚ΠΎΡ€Ρ‹ΠΉ ΠΈ Π±ΡƒΠ΄Π΅Ρ‚ использован для получСния hash-значСния ΠΏΠ»Π°Π½Π°, Ρ‡Ρ‚ΠΎ ΠΏΠΎΠΌΠΎΠΆΠ΅Ρ‚ ΠΏΡ€ΠΈ ΠΏΠΎΡΠ»Π΅Π΄ΡƒΡŽΡ‰Π΅ΠΌ Π°Π½Π°Π»ΠΈΠ·Π΅ истории измСнСния ΠΏΠ»Π°Π½Π° выполнСния.
ΠŸΠΎΠ»ΡƒΡ‡ΠΈΡ‚ΡŒ шаблон ΠΏΠ»Π°Π½Π° выполнСния

  --Explain execution plan--
  EXECUTE 'SELECT dblink_connect(''LINK1'',''host='||database_rec.host||' dbname='||database_rec.name||' user=DATABASE password='||database_rec.owner_pwd||' '')'; 
  
  log_explain_plan = ARRAY ( SELECT * FROM dblink('LINK1', 'EXPLAIN '||log_query ) AS t (plan text) );
  log_plan_wo_costs = ARRAY ( SELECT * FROM dblink('LINK1', 'EXPLAIN ( COSTS FALSE ) '||log_query ) AS t (plan text) );
    
  PERFORM dblink_disconnect('LINK1');

Π’ΠΎΠ·ΠΌΠΎΠΆΠ½ΠΎΡΡ‚ΡŒ β„–3 β€” ИспользованиС Π»ΠΎΠ³Π° запросов для ΠΌΠΎΠ½ΠΈΡ‚ΠΎΡ€ΠΈΠ½Π³Π°

ΠŸΠΎΡΠΊΠΎΠ»ΡŒΠΊΡƒ ΠΌΠ΅Ρ‚Ρ€ΠΈΠΊΠΈ ΠΏΡ€ΠΎΠΈΠ·Π²ΠΎΠ΄ΠΈΡ‚Π΅Π»ΡŒΠ½ΠΎΡΡ‚ΠΈ настроСны Π½Π΅ Π½Π° тСкст запроса, Π° Π½Π° Π΅Π³ΠΎ ID, Π½ΡƒΠΆΠ½ΠΎ ΡΠ²ΡΠ·Ρ‹Π²Π°Ρ‚ΡŒ запросы ΠΈΠ· Π»ΠΎΠ³-Ρ„Π°ΠΉΠ»Π° с запросами для ΠΊΠΎΡ‚ΠΎΡ€Ρ‹Ρ… настроСны ΠΌΠ΅Ρ‚Ρ€ΠΈΠΊΠΈ ΠΏΡ€ΠΎΠΈΠ·Π²ΠΎΠ΄ΠΈΡ‚Π΅Π»ΡŒΠ½ΠΎΡΡ‚ΠΈ.
Ну хотя Π±Ρ‹ для Ρ‚ΠΎΠ³ΠΎ, Ρ‡Ρ‚ΠΎΠ±Ρ‹ ΠΈΠΌΠ΅Ρ‚ΡŒ Ρ‚ΠΎΡ‡Π½ΠΎΠ΅ врСмя возникновСния ΠΈΠ½Ρ†ΠΈΠ΄Π΅Π½Ρ‚Π° ΠΏΡ€ΠΎΠΈΠ·Π²ΠΎΠ΄ΠΈΡ‚Π΅Π»ΡŒΠ½ΠΎΡΡ‚ΠΈ.

Π’Π°ΠΊΠΈΠΌ ΠΎΠ±Ρ€Π°Π·ΠΎΠΌ, ΠΏΡ€ΠΈ Π²ΠΎΠ·Π½ΠΈΠΊΠ½ΠΎΠ²Π΅Π½ΠΈΠΈ ΠΈΠ½Ρ†ΠΈΠ΄Π΅Π½Ρ‚Π° ΠΏΡ€ΠΎΠΈΠ·Π²ΠΎΠ΄ΠΈΡ‚Π΅Π»ΡŒΠ½ΠΎΡΡ‚ΠΈ для ID запроса, Π±ΡƒΠ΄Π΅Ρ‚ ссылка Π½Π° ΠΊΠΎΠ½ΠΊΡ€Π΅Ρ‚Π½Ρ‹ΠΉ запрос с ΠΊΠΎΠ½ΠΊΡ€Π΅Ρ‚Π½Ρ‹ΠΌΠΈ значСниями ΠΏΠ°Ρ€Π°ΠΌΠ΅Ρ‚Ρ€Π° ΠΈ Ρ‚ΠΎΡ‡Π½Ρ‹ΠΌ Π²Ρ€Π΅ΠΌΠ΅Π½Π΅ΠΌ выполнСния ΠΈ Π΄Π»ΠΈΡ‚Π΅Π»ΡŒΠ½ΠΎΡΡ‚ΠΈ запроса. ΠŸΠΎΠ»ΡƒΡ‡ΠΈΡ‚ΡŒ Π΄Π°Π½Π½ΡƒΡŽ ΠΈΠ½Ρ„ΠΎΡ€ΠΌΠ°Ρ†ΠΈΡŽ ΠΈΡΠΏΠΎΠ»ΡŒΠ·ΡƒΡ Ρ‚ΠΎΠ»ΡŒΠΊΠΎ прСдставлСниС pg_stat_statements β€” нСльзя.
Найти queryid запроса ΠΈ ΠΎΠ±Π½ΠΎΠ²ΠΈΡ‚ΡŒ запись Π² Ρ‚Π°Π±Π»ΠΈΡ†Π΅ log_query

SELECT * 
	INTO log_query_rec
	FROM log_query
	WHERE query_md5hash = log_md5hash AND timepoint = log_timepoint ; 
	
	log_query_rec.query=regexp_replace(log_query_rec.query,';+','','g');
	
	FOR pg_stat_history_rec IN
	 SELECT 
      queryid ,
	  query 
	 FROM 
       pg_stat_db_queries 
     WHERE  
	   database_id = log_database_id AND
       queryid is not null 
	LOOP
	  pg_stat_query = pg_stat_history_rec.query ; 
	  pg_stat_query=regexp_replace(pg_stat_query,'n+',' ','g');
	  pg_stat_query=regexp_replace(pg_stat_query,'t+',' ','g');
	  pg_stat_query=regexp_replace(pg_stat_query,' +',' ','g');
	  pg_stat_query=regexp_replace(pg_stat_query,'$.','%','g');
	
	  log_query_text = trim(trailing ' ' from log_query_rec.query);
	  pg_stat_query_text = pg_stat_query; 
	  
	  --SELECT log_query_rec.query like pg_stat_query INTO found_flag ; 
	  IF (log_query_text LIKE pg_stat_query_text) THEN
		found_flag = TRUE ;
	  ELSE
		found_flag = FALSE ;
	  END IF;	  
	  
	  
	  IF found_flag THEN
	    
		UPDATE log_query SET queryid = pg_stat_history_rec.queryid WHERE query_md5hash = log_md5hash AND timepoint = log_timepoint ;
		activity_string = 	' updated queryid = '||pg_stat_history_rec.queryid||
		                    ' for log_query with id = '||log_query_rec.id		                    
		   				    ;						
					
	    RAISE NOTICE '%',activity_string;	
		EXIT ;
	  END IF ;
	  
	END LOOP ;

ПослСсловиС

Описанная ΠΌΠ΅Ρ‚ΠΎΠ΄ΠΈΠΊΠ° Π² ΠΈΡ‚ΠΎΠ³Π΅, нашла сСбС ΠΏΡ€ΠΈΠΌΠ΅Π½Π΅Π½ΠΈΠ΅ Π² Ρ€Π°Π·Ρ€Π°Π±Π°Ρ‚Ρ‹Π²Π°Π΅ΠΌΠΎΠΉ систСмС ΠΌΠΎΠ½ΠΈΡ‚ΠΎΡ€ΠΈΠ½Π³Π° ΠΏΡ€ΠΎΠΈΠ·Π²ΠΎΠ΄ΠΈΡ‚Π΅Π»ΡŒΠ½ΠΎΡΡ‚ΠΈ запросов PostgreSQL, ΠΏΠΎΠ·Π²ΠΎΠ»ΠΈΠ² ΠΈΠΌΠ΅Ρ‚ΡŒ большС ΠΈΠ½Ρ„ΠΎΡ€ΠΌΠ°Ρ†ΠΈΠΈ для Π°Π½Π°Π»ΠΈΠ·Π° ΠΏΡ€ΠΈ Ρ€Π΅ΡˆΠ΅Π½ΠΈΠΈ Π²ΠΎΠ·Π½ΠΈΠΊΠ°ΡŽΡ‰ΠΈΡ… ΠΈΠ½Ρ†ΠΈΠ΄Π΅Π½Ρ‚ΠΎΠ² ΠΏΡ€ΠΎΠΈΠ·Π²ΠΎΠ΄ΠΈΡ‚Π΅Π»ΡŒΠ½ΠΎΡΡ‚ΠΈ запросов.

Π₯отя, ΠΊΠΎΠ½Π΅Ρ‡Π½ΠΎ, Π½Π° ΠΌΠΎΠΉ Π»ΠΈΡ‡Π½Ρ‹ΠΉ авторский взгляд, Π½ΡƒΠΆΠ½ΠΎ Π±ΡƒΠ΄Π΅Ρ‚ Π΅Ρ‰Π΅ ΠΏΠΎΡ€Π°Π±ΠΎΡ‚Π°Ρ‚ΡŒ Π½Π°Π΄ Π°Π»Π³ΠΎΡ€ΠΈΡ‚ΠΌΠΎΠΌ Π²Ρ‹Π±ΠΎΡ€Π° ΠΈ измСнСния Ρ€Π°Π·ΠΌΠ΅Ρ€Π° Π·Π°Π³Ρ€ΡƒΠΆΠ°Π΅ΠΌΠΎΠΉ ΠΏΠΎΡ€Ρ†ΠΈΠΈ. Π—Π°Π΄Π°Ρ‡Π° ΠΏΠΎΠΊΠ° Π½Π΅ Ρ€Π΅ΡˆΠ΅Π½Π° Π² ΠΎΠ±Ρ‰Π΅ΠΌ случаС. НавСрноС, Π±ΡƒΠ΄Π΅Ρ‚ интСрСсно.

Но это ΡƒΠΆΠ΅ совсСм другая история …

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

Π”ΠΎΠ±Π°Π²ΠΈΡ‚ΡŒ ΠΊΠΎΠΌΠΌΠ΅Π½Ρ‚Π°Ρ€ΠΈΠΉ