Π‘ΠΎΠ»ΡŒΡˆΠ΅ статистики сайта Π² своём малСньком Ρ…Ρ€Π°Π½ΠΈΠ»ΠΈΡ‰Π΅

Анализируя статистику сайта, ΠΌΡ‹ ΠΏΠΎΠ»ΡƒΡ‡Π°Π΅ΠΌ прСдставлСниС ΠΎ Ρ‚ΠΎΠΌ, Ρ‡Ρ‚ΠΎ происходит с Π½ΠΈΠΌ. Π Π΅Π·ΡƒΠ»ΡŒΡ‚Π°Ρ‚Ρ‹ ΠΌΡ‹ сопоставляСм с Π΄Ρ€ΡƒΠ³ΠΈΠΌΠΈ знаниями ΠΎ ΠΏΡ€ΠΎΠ΄ΡƒΠΊΡ‚Π΅ ΠΈΠ»ΠΈ сСрвисС ΠΈ этим ΡƒΠ»ΡƒΡ‡ΡˆΠ°Π΅ΠΌ наш ΠΎΠΏΡ‹Ρ‚.

Когда Π°Π½Π°Π»ΠΈΠ· ΠΏΠ΅Ρ€Π²Ρ‹Ρ… Ρ€Π΅Π·ΡƒΠ»ΡŒΡ‚Π°Ρ‚ΠΎΠ² Π·Π°Π²Π΅Ρ€ΡˆΡ‘Π½, ΠΏΡ€ΠΎΡˆΠ»ΠΎ осмыслСниС ΠΈΠ½Ρ„ΠΎΡ€ΠΌΠ°Ρ†ΠΈΠΈ ΠΈ сдСланы Π²Ρ‹Π²ΠΎΠ΄Ρ‹, начинаСтся ΡΠ»Π΅Π΄ΡƒΡŽΡ‰ΠΈΠΉ этап. Π’ΠΎΠ·Π½ΠΈΠΊΠ°ΡŽΡ‚ ΠΈΠ΄Π΅ΠΈ: Π° Ρ‡Ρ‚ΠΎ Π±ΡƒΠ΄Π΅Ρ‚, Ссли ΠΏΠΎΡΠΌΠΎΡ‚Ρ€Π΅Ρ‚ΡŒ Π½Π° Π΄Π°Π½Π½Ρ‹Π΅ с Π΄Ρ€ΡƒΠ³ΠΎΠΉ стороны?

На этом этапС Π΅ΡΡ‚ΡŒ ограничСния инструмСнтов Π°Π½Π°Π»ΠΈΠ·Π°. Π­Ρ‚ΠΎ ΠΎΠ΄Π½Π° ΠΈΠ· ΠΏΡ€ΠΈΡ‡ΠΈΠ½, ΠΏΠΎΡ‡Π΅ΠΌΡƒ ΠΌΠ½Π΅ Π±Ρ‹Π»ΠΎ нСдостаточно инструмСнта Google Analytics, Π° ΠΈΠΌΠ΅Π½Π½ΠΎ, ΠΈΠ·-Π·Π° ΠΎΠ³Ρ€Π°Π½ΠΈΡ‡Π΅Π½Π½ΠΎΠΉ возмоТности Π²ΠΈΠ΄Π΅Ρ‚ΡŒ свои Π΄Π°Π½Π½Ρ‹Π΅ ΠΈ ΠΌΠ°Π½ΠΈΠΏΡƒΠ»ΠΈΡ€ΠΎΠ²Π°Ρ‚ΡŒ ΠΈΠΌΠΈ.

ВсСгда Ρ…ΠΎΡ‚Π΅Π»ΠΎΡΡŒ быстро Π·Π°Π³Ρ€ΡƒΠ·ΠΈΡ‚ΡŒ Π±Π°Π·ΠΎΠ²Ρ‹Π΅ Π΄Π°Π½Π½Ρ‹Π΅ (мастСр-Π΄Π°Π½Π½Ρ‹Π΅), Π΄ΠΎΠ±Π°Π²ΠΈΡ‚ΡŒ Π΄Ρ€ΡƒΠ³ΠΎΠΉ ΡƒΡ€ΠΎΠ²Π΅Π½ΡŒ Π°Π³Ρ€Π΅Π³Π°Ρ†ΠΈΠΈ ΠΈΠ»ΠΈ ΠΈΠ½Π°Ρ‡Π΅ ΠΈΠ½Ρ‚Π΅Ρ€ΠΏΡ€Π΅Ρ‚ΠΈΡ€ΠΎΠ²Π°Ρ‚ΡŒ ΠΈΠΌΠ΅ΡŽΡ‰ΠΈΠ΅ΡΡ значСния.

Π­Ρ‚ΠΎ Π»Π΅Π³ΠΊΠΎ ΡΠ΄Π΅Π»Π°Ρ‚ΡŒ Π² своём малСньком Ρ…Ρ€Π°Π½ΠΈΠ»ΠΈΡ‰Π΅ Π½Π° основС Ρ„Π°ΠΉΠ»Π° access.log ΠΈ для этого достаточно языка SQL.

Π˜Ρ‚Π°ΠΊ, Π½Π° ΠΊΠ°ΠΊΠΈΠ΅ вопросы ΠΌΠ½Π΅ Ρ…ΠΎΡ‚Π΅Π»ΠΎΡΡŒ Π½Π°ΠΉΡ‚ΠΈ ΠΎΡ‚Π²Π΅Ρ‚?

Π§Ρ‚ΠΎ ΠΈ ΠΊΠΎΠ³Π΄Π° измСнялось Π½Π° сайтС

Π˜ΡΡ‚ΠΎΡ€ΠΈΡ ΠΈΠ·ΠΌΠ΅Π½Π΅Π½ΠΈΠΉ Π±Π°Π·ΠΎΠ²Ρ‹Ρ… Π΄Π°Π½Π½Ρ‹Ρ… (мастСр-Π΄Π°Π½Π½Ρ‹Ρ…) всСгда прСдставляСт собой интСрСс.

Π‘ΠΎΠ»ΡŒΡˆΠ΅ статистики сайта Π² своём малСньком Ρ…Ρ€Π°Π½ΠΈΠ»ΠΈΡ‰Π΅

SQL запрос ΠΎΡ‚Ρ‡Ρ‘Ρ‚Π°

SELECT
	1 as 'SideStackedBar: Content Updates by Months',
	strftime('%m/%Y', datetime(UPDATE_DT, 'unixepoch')) AS 'Day',
	COUNT(CASE WHEN PAGE_TITLE != 'n.a.' THEN DIM_REQUEST_ID END) AS 'Web page updates',
	COUNT(CASE WHEN PAGE_DESCR = 'IMAGES' THEN DIM_REQUEST_ID END) AS 'Image uploads',
	COUNT(CASE WHEN PAGE_DESCR = 'VIDEO' THEN DIM_REQUEST_ID END) AS 'Video uploads',
	COUNT(CASE WHEN PAGE_DESCR = 'AUDIO' THEN DIM_REQUEST_ID END) AS 'Audio uploads'
FROM DIM_REQUEST
WHERE PAGE_TITLE != 'n.a.' OR PAGE_DESCR != 'n.a.'
GROUP BY strftime('%m/%Y', datetime(UPDATE_DT, 'unixepoch'))
ORDER BY UPDATE_DT

НапримСр, Π² ΠΊΠ°ΠΊΠΎΠΉ-Ρ‚ΠΎ ΠΌΠΎΠΌΠ΅Π½Ρ‚ Π±Ρ‹Π»Π° ΠΏΡ€ΠΎΠ²Π΅Π΄Π΅Π½Π° поисковая оптимизация ΠΈΠ»ΠΈ Π΄ΠΎΠ±Π°Π²Π»Π΅Π½ΠΎ Π½ΠΎΠ²ΠΎΠ΅ содСрТимоС Π½Π° сайт, Π² связи с этим оТидаСтся ΡƒΠ²Π΅Π»ΠΈΡ‡Π΅Π½ΠΈΠ΅ Ρ‚Ρ€Π°Ρ„ΠΈΠΊΠ°.

Π“Ρ€ΡƒΠΏΠΏΡ‹ ΠΏΠΎΠ»ΡŒΠ·ΠΎΠ²Π°Ρ‚Π΅Π»Π΅ΠΉ

Π‘Π°ΠΌΡ‹ΠΌ простым ΠΏΡ€ΠΈΠΌΠ΅Ρ€ΠΎΠΌ Π³Ρ€ΡƒΠΏΠΏΡ‹ ΠΌΠΎΠΆΠ΅Ρ‚ ΡΠ»ΡƒΠΆΠΈΡ‚ΡŒ ΠΏΠΎΠ»ΡŒΠ·ΠΎΠ²Π°Ρ‚Π΅Π»ΡŒΡΠΊΠΈΠΉ Π°Π³Π΅Π½Ρ‚ ΠΈΠ»ΠΈ Π½Π°Π·Π²Π°Π½ΠΈΠ΅ ΠΎΠΏΠ΅Ρ€Π°Ρ‚ΠΈΠ²Π½ΠΎΠΉ систСмы.

Π˜Π·ΠΌΠ΅Ρ€Π΅Π½ΠΈΠ΅ ΠΏΠΎΠ»ΡŒΠ·ΠΎΠ²Π°Ρ‚Π΅Π»ΡŒΡΠΊΠΈΡ… Π°Π³Π΅Π½Ρ‚ΠΎΠ² Π½Π°ΠΊΠΎΠΏΠΈΠ»ΠΎ Π² сСбС ΠΎΠΊΠΎΠ»ΠΎ тысячи записСй ΠΈ ΠΌΠ½Π΅ интСрСсно Π±Ρ‹Π»ΠΎ ΡƒΠ²ΠΈΠ΄Π΅Ρ‚ΡŒ Π΄ΠΈΠ½Π°ΠΌΠΈΠΊΡƒ распрСдСлСния Π°Π³Π΅Π½Ρ‚ΠΎΠ² Π² ΠΏΡ€Π΅Π΄Π΅Π»Π°Ρ… Π³Ρ€ΡƒΠΏΠΏΡ‹.

Π‘ΠΎΠ»ΡŒΡˆΠ΅ статистики сайта Π² своём малСньком Ρ…Ρ€Π°Π½ΠΈΠ»ΠΈΡ‰Π΅

SQL запрос ΠΎΡ‚Ρ‡Ρ‘Ρ‚Π°

SELECT
	1 AS 'SideStackedBar: User Agents',
	AGENT_OS AS 'OS',
	SUM(CASE WHEN AGENT_BOT = 'n.a.' THEN 1 ELSE 0 END ) AS 'User Agent of Users',
	SUM(CASE WHEN AGENT_BOT != 'n.a.' THEN 1 ELSE 0 END ) AS 'User Agent of Bots'
FROM DIM_USER_AGENT
WHERE DIM_USER_AGENT_ID != -1
GROUP BY AGENT_OS
ORDER BY 3 DESC

Π‘ΠΎΠ»ΡŒΡˆΠ΅ всСго Ρ€Π°Π·Π»ΠΈΡ‡Π½Ρ‹Ρ… ΠΊΠΎΠΌΠ±ΠΈΠ½Π°Ρ†ΠΈΠΉ Π°Π³Π΅Π½Ρ‚ΠΎΠ² ΠΏΡ€ΠΈΡ…ΠΎΠ΄ΠΈΡ‚ Π½Π° сайт ΠΈΠ· ΠΌΠΈΡ€Π° Windows. Π’ числС Π½Π΅ΠΎΠΏΡ€Π΅Π΄Π΅Π»Ρ‘Π½Π½Ρ‹Ρ… оказались Ρ‚Π°ΠΊΠΈΠ΅, ΠΊΠ°ΠΊ WhatsApp, PocketImageCache, PlayStation, SmartTV ΠΈ Ρ‚.ΠΏ.

ΠΠΊΡ‚ΠΈΠ²Π½ΠΎΡΡ‚ΡŒ Π³Ρ€ΡƒΠΏΠΏ ΠΏΠΎΠ»ΡŒΠ·ΠΎΠ²Π°Ρ‚Π΅Π»Π΅ΠΉ ΠΏΠΎ нСдСлям

ОбъСдинив Π½Π΅ΠΊΠΎΡ‚ΠΎΡ€Ρ‹Π΅ Π³Ρ€ΡƒΠΏΠΏΡ‹, ΠΌΠΎΠΆΠ½ΠΎ Π½Π°Π±Π»ΡŽΠ΄Π°Ρ‚ΡŒ распрСдСлСниС ΠΈΡ… активности.

НапримСр, ΠΏΠΎΠ»ΡŒΠ·ΠΎΠ²Π°Ρ‚Π΅Π»ΠΈ кластСра Linux ΠΏΠΎΡ‚Ρ€Π΅Π±Π»ΡΡŽΡ‚ большС Ρ‚Ρ€Π°Ρ„ΠΈΠΊΠ° Π½Π° сайтС, Ρ‡Π΅ΠΌ всС ΠΎΡΡ‚Π°Π»ΡŒΠ½Ρ‹Π΅.

Π‘ΠΎΠ»ΡŒΡˆΠ΅ статистики сайта Π² своём малСньком Ρ…Ρ€Π°Π½ΠΈΠ»ΠΈΡ‰Π΅

SQL запрос ΠΎΡ‚Ρ‡Ρ‘Ρ‚Π°

SELECT
1 as 'StackedBar: Traffic Volume by User OS and by Week',
strftime('%W week', datetime(FCT.EVENT_DT, 'unixepoch')) AS 'Week',
SUM(CASE WHEN USG.AGENT_OS IN ('Android', 'Linux') THEN FCT.BYTES ELSE 0 END)/1000 AS 'Android/Linux Users',
SUM(CASE WHEN USG.AGENT_OS IN ('Windows') THEN FCT.BYTES ELSE 0 END)/1000 AS 'Windows Users',
SUM(CASE WHEN USG.AGENT_OS IN ('Macintosh', 'iOS') THEN FCT.BYTES ELSE 0 END)/1000 AS 'Mac/iOS Users',
SUM(CASE WHEN USG.AGENT_OS IN ('n.a.', 'BlackBerry') THEN FCT.BYTES ELSE 0 END)/1000 AS 'Other'
FROM
  FCT_ACCESS_USER_AGENT_DD FCT,
  DIM_USER_AGENT USG,
  DIM_HTTP_STATUS HST
WHERE FCT.DIM_USER_AGENT_ID=USG.DIM_USER_AGENT_ID
  AND FCT.DIM_HTTP_STATUS_ID = HST.DIM_HTTP_STATUS_ID
  AND USG.AGENT_BOT = 'n.a.' /* users only */
  AND HST.STATUS_GROUP IN ('Successful') /* good pages */
  AND datetime(FCT.EVENT_DT, 'unixepoch') > date('now', '-3 month')
GROUP BY strftime('%W week', datetime(FCT.EVENT_DT, 'unixepoch'))
ORDER BY FCT.EVENT_DT

Π˜Π½Ρ‚Π΅Π½ΡΠΈΠ²Π½ΠΎΠ΅ ΠΏΠΎΡ‚Ρ€Π΅Π±Π»Π΅Π½ΠΈΠ΅ Ρ‚Ρ€Π°Ρ„ΠΈΠΊΠ°

Из Ρ‚Π°Π±Π»ΠΈΡ†Ρ‹ Π²ΠΈΠ΄Π½Ρ‹ Π½Π°ΠΈΠ±ΠΎΠ»Π΅Π΅ Π°ΠΊΡ‚ΠΈΠ²Π½Ρ‹Π΅ Π³Ρ€ΡƒΠΏΠΏΡ‹ ΠΏΠΎΠ»ΡŒΠ·ΠΎΠ²Π°Ρ‚Π΅Π»Π΅ΠΉ ΠΈ дСнь ΠΈΡ… активности.
НаиболСС Π°ΠΊΡ‚ΠΈΠ²Π½Ρ‹Π΅ относятся ΠΊ Linux кластСру.

Π‘ΠΎΠ»ΡŒΡˆΠ΅ статистики сайта Π² своём малСньком Ρ…Ρ€Π°Π½ΠΈΠ»ΠΈΡ‰Π΅

SQL запрос ΠΎΡ‚Ρ‡Ρ‘Ρ‚Π°

SELECT
1 AS 'Table: User Agent with Havy Usage',
strftime('%d.%m.%Y', datetime(FCT.EVENT_DT, 'unixepoch')) AS 'Day',
ROUND(1.0*SUM(FCT.BYTES)/1000000, 1) AS 'Traffic MB',
ROUND(1.0*SUM(FCT.IP_CNT)/SUM(1), 1) AS 'IPs',
ROUND(1.0*SUM(FCT.REQUEST_CNT)/SUM(1), 1) AS 'Requests',
USA.DIM_USER_AGENT_ID AS 'ID',
MAX(USA.USER_AGENT_NK) AS 'User Agent',
MAX(USA.AGENT_BOT) AS 'Bot'
FROM
FCT_ACCESS_USER_AGENT_DD FCT,
DIM_USER_AGENT USA
WHERE FCT.DIM_USER_AGENT_ID = USA.DIM_USER_AGENT_ID
  AND datetime(FCT.EVENT_DT, 'unixepoch') >= date('now', '-30 day')
GROUP BY USA.DIM_USER_AGENT_ID, strftime('%d.%m.%Y', datetime(FCT.EVENT_DT, 'unixepoch')) 
ORDER BY SUM(FCT.BYTES) DESC, FCT.EVENT_DT
LIMIT 10

Π˜ΡΠΏΠΎΠ»ΡŒΠ·ΡƒΡ Π°Ρ‚Ρ€ΠΈΠ±ΡƒΡ‚Ρ‹ дСнь ΠΈ ID Π°Π³Π΅Π½Ρ‚Π°, появляСтся Π²ΠΎΠ·ΠΌΠΎΠΆΠ½ΠΎΡΡ‚ΡŒ быстро Π½Π°ΠΉΡ‚ΠΈ ΠΈ ΠΏΡ€ΠΎΡΠ»Π΅Π΄ΠΈΡ‚ΡŒ статистику ΠΏΠΎ дням ΠΎΡ‚Π΄Π΅Π»ΡŒΠ½Ρ‹Ρ… Π³Ρ€ΡƒΠΏΠΏ ΠΏΠΎΠ»ΡŒΠ·ΠΎΠ²Π°Ρ‚Π΅Π»Π΅ΠΉ. Π’ случаС нСобходимости, ΠΌΠΎΠΆΠ½ΠΎ быстро Π½Π°ΠΉΡ‚ΠΈ Π΄Π΅Ρ‚Π°Π»ΡŒΠ½ΡƒΡŽ ΠΈΠ½Ρ„ΠΎΡ€ΠΌΠ°Ρ†ΠΈΡŽ Π² стСйдТ Ρ‚Π°Π±Π»ΠΈΡ†Π΅.

Как ΠΏΠΎΠ»ΡƒΡ‡ΠΈΡ‚ΡŒ ΠΈΠ½Ρ„ΠΎΡ€ΠΌΠ°Ρ†ΠΈΡŽ?

Π˜Π½Ρ„ΠΎΡ€ΠΌΠ°Ρ†ΠΈΡŽ ΠΈΠ· Ρ„Π°ΠΉΠ»Π° access.log ΠΌΠΎΠΆΠ½ΠΎ ΡΠ΄Π΅Π»Π°Ρ‚ΡŒ Π΅Ρ‰Ρ‘ эффСктивнСС, Ссли ΠΈΠ½Ρ‚Π΅Π³Ρ€ΠΈΡ€ΠΎΠ²Π°Ρ‚ΡŒ Π΄ΠΎΠΏΠΎΠ»Π½ΠΈΡ‚Π΅Π»ΡŒΠ½Ρ‹Π΅ источники Π΄Π°Π½Π½Ρ‹Ρ…, ввСсти Π½ΠΎΠ²Ρ‹Π΅ ΡƒΡ€ΠΎΠ²Π½ΠΈ Π°Π³Ρ€Π΅Π³Π°Ρ†ΠΈΠΈ ΠΈ Π³Ρ€ΡƒΠΏΠΏΠΈΡ€ΠΎΠ²ΠΊΠΈ.

Π‘Π°Π·ΠΎΠ²Ρ‹Π΅ Π΄Π°Π½Π½Ρ‹Π΅ ΠΈ сущности

К Π±Π°Π·ΠΎΠ²Ρ‹ΠΌ Π΄Π°Π½Π½Ρ‹ΠΌ относится информация ΠΎ сущностях: Π²Π΅Π± страницы, ΠΊΠ°Ρ€Ρ‚ΠΈΠ½ΠΊΠΈ, Π²ΠΈΠ΄Π΅ΠΎ ΠΈ Π°ΡƒΠ΄ΠΈΠΎ содСрТимоС, Π² случаС ΠΌΠ°Π³Π°Π·ΠΈΠ½Π° β€” ΠΏΡ€ΠΎΠ΄ΡƒΠΊΡ‚Ρ‹.

Π‘Π°ΠΌΠΈ сущности Π²Ρ‹ΠΏΠΎΠ»Π½ΡΡŽΡ‚ Ρ€ΠΎΠ»ΡŒ ΠΈΠ·ΠΌΠ΅Ρ€Π΅Π½ΠΈΠΉ, Π° процСсс сохранСния ΠΈΠ·ΠΌΠ΅Π½Π΅Π½ΠΈΠΉ Π°Ρ‚Ρ€ΠΈΠ±ΡƒΡ‚ΠΎΠ² Π½Π°Π·Ρ‹Π²Π°ΡŽΡ‚ историзациСй. Π’ Π±Π°Π·Π΅ Π΄Π°Π½Π½Ρ‹Ρ… этот процСсс часто Ρ€Π΅Π°Π»ΠΈΠ·ΡƒΡŽΡ‚ Π² Ρ„ΠΎΡ€ΠΌΠ΅ ΠΌΠ΅Π΄Π»Π΅Π½Π½ΠΎ ΠΈΠ·ΠΌΠ΅Π½ΡΡŽΡ‰ΠΈΡ…ΡΡ ΠΈΠ·ΠΌΠ΅Ρ€Π΅Π½ΠΈΠΉ (SCD).

Π˜ΡΡ‚ΠΎΡ‡Π½ΠΈΠΊΠΎΠΌ Π±Π°Π·ΠΎΠ²Ρ‹Ρ… Π΄Π°Π½Π½Ρ‹Ρ… ΠΌΠΎΠ³ΡƒΡ‚ Π±Ρ‹Ρ‚ΡŒ самыС Ρ€Π°Π·Π½Ρ‹Π΅ систСмы, поэтому ΠΏΠΎΡ‡Ρ‚ΠΈ всСгда ΠΈΡ… Π½ΡƒΠΆΠ½ΠΎ ΠΈΠ½Ρ‚Π΅Π³Ρ€ΠΈΡ€ΠΎΠ²Π°Ρ‚ΡŒ.

МСдлСнно ΠΈΠ·ΠΌΠ΅Π½ΡΡŽΡ‰Π΅Π΅ΡΡ ΠΈΠ·ΠΌΠ΅Ρ€Π΅Π½ΠΈΠ΅

Π˜Π·ΠΌΠ΅Ρ€Π΅Π½ΠΈΠ΅ DIM_REQUEST Π±ΡƒΠ΄Π΅Ρ‚ ΡΠΎΠ΄Π΅Ρ€ΠΆΠ°Ρ‚ΡŒ ΠΈΠ½Ρ„ΠΎΡ€ΠΌΠ°Ρ†ΠΈΡŽ ΠΎ запросах Π½Π° сайтС Π² историчСской Ρ„ΠΎΡ€ΠΌΠ΅.

Π’Π°Π±Π»ΠΈΡ†Π° SCD2

CREATE TABLE DIM_REQUEST ( /* scd table for user requests */
  DIM_REQUEST_ID      INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
  DIM_REQUEST_ID_HIST INTEGER NOT NULL DEFAULT -1,
  REQUEST_NK          TEXT NOT NULL DEFAULT 'n.a.', /* request without ?parameters */
  PAGE_TITLE          TEXT NOT NULL DEFAULT 'n.a.',
  PAGE_DESCR          TEXT NOT NULL DEFAULT 'n.a.',
  PAGE_KEYWORDS       TEXT NOT NULL DEFAULT 'n.a.',
  DELETE_FLAG         INTEGER NOT NULL DEFAULT 0,
  UPDATE_DT           INTEGER NOT NULL DEFAULT 0,
  UNIQUE (REQUEST_NK, DIM_REQUEST_ID_HIST)
);
INSERT INTO DIM_REQUEST (DIM_REQUEST_ID) VALUES (-1);

Π”ΠΎΠΏΠΎΠ»Π½ΠΈΡ‚Π΅Π»ΡŒΠ½ΠΎ ΠΊ Π½Π΅ΠΌΡƒ создадим ΠΎΠ΄Π½ΠΎ прСдставлСниС, ΠΊΠΎΡ‚ΠΎΡ€ΠΎΠ΅ всСгда ΠΎΡ‚ΠΎΠ±Ρ€Π°ΠΆΠ°Π΅Ρ‚ всС записи Π² послСднСм состоянии. НСобходимо для Π·Π°Π³Ρ€ΡƒΠ·ΠΊΠΈ самого измСрСния.

Π‘ΠΎΠ»ΡŒΡˆΠ΅ статистики сайта Π² своём малСньком Ρ…Ρ€Π°Π½ΠΈΠ»ΠΈΡ‰Π΅

ΠΠΊΡ‚ΡƒΠ°Π»ΡŒΠ½ΠΎΠ΅ прСдставлСниС SCD2

/* Content: actual view on scd table */
SELECT HI.DIM_REQUEST_ID,
  HI.DIM_REQUEST_ID_HIST,
  HI.REQUEST_NK,
  HI.PAGE_TITLE,
  HI.PAGE_DESCR,
  HI.PAGE_KEYWORDS,
  NK.CNT AS HIST_CNT,
  HI.DELETE_FLAG,
  strftime('%d.%m.%Y %H:%M', datetime(HI.UPDATE_DT, 'unixepoch')) AS UPDATE_DT
FROM
  ( SELECT REQUEST_NK, MAX(DIM_REQUEST_ID) AS DIM_REQUEST_ID, SUM(1) AS CNT
    FROM DIM_REQUEST
    GROUP BY REQUEST_NK
  ) NK,
  DIM_REQUEST HI
WHERE 1 = 1
  AND NK.REQUEST_NK = HI.REQUEST_NK
  AND NK.DIM_REQUEST_ID = HI.DIM_REQUEST_ID;

И прСдставлСниС, Π³Π΄Π΅ для ΠΊΠ°ΠΆΠ΄ΠΎΠΉ записи собрана историчСская информация. НСобходимо для построСния историчСски Π²Π΅Ρ€Π½ΠΎΠΉ связи с Ρ„Π°ΠΊΡ‚Π°ΠΌΠΈ.

Π‘ΠΎΠ»ΡŒΡˆΠ΅ статистики сайта Π² своём малСньком Ρ…Ρ€Π°Π½ΠΈΠ»ΠΈΡ‰Π΅

Π˜ΡΡ‚ΠΎΡ€ΠΈΡ‡Π΅ΡΠΊΠΎΠ΅ прСдставлСниС SCD2

/* Content: actual view on scd table */
SELECT SCD.DIM_REQUEST_ID,
  SCD.DIM_REQUEST_ID_HIST,
  SCD.REQUEST_NK,
  SCD.PAGE_TITLE,
  SCD.PAGE_DESCR,
  SCD.PAGE_KEYWORDS,
  SCD.DELETE_FLAG,
  CASE
    WHEN HIS.UPDATE_DT IS NULL
    THEN 1
    ELSE 0 END ACTIVE_FLAG,
  SCD.DIM_REQUEST_ID_HIST AS ID_FROM,
  SCD.DIM_REQUEST_ID AS ID_TO,
  CASE
    WHEN SCD.DIM_REQUEST_ID_HIST=-1
    THEN 3600
    ELSE IFNULL(SCD.UPDATE_DT,3600)
  END AS TIME_FROM,
  CASE
    WHEN HIS.UPDATE_DT IS NULL
    THEN 253370764800
    ELSE HIS.UPDATE_DT
  END AS TIME_TO,
  CASE
    WHEN SCD.DIM_REQUEST_ID_HIST=-1
    THEN STRFTIME('%d.%m.%Y %H:%M', DATETIME(3600, 'unixepoch'))
    ELSE STRFTIME('%d.%m.%Y %H:%M', DATETIME(IFNULL(SCD.UPDATE_DT,3600), 'unixepoch'))
  END AS ACTIVE_FROM,
  CASE
    WHEN HIS.UPDATE_DT IS NULL
    THEN STRFTIME('%d.%m.%Y %H:%M', DATETIME(253370764800, 'unixepoch'))
    ELSE STRFTIME('%d.%m.%Y %H:%M', DATETIME(HIS.UPDATE_DT, 'unixepoch'))
  END AS ACTIVE_TO
FROM
  DIM_REQUEST SCD
  LEFT OUTER JOIN DIM_REQUEST HIS
  ON SCD.REQUEST_NK = HIS.REQUEST_NK AND SCD.DIM_REQUEST_ID = HIS.DIM_REQUEST_ID_HIST;

АгрСгация Π΄Π°Π½Π½Ρ‹Ρ…

Π‘ΠΆΠ°Ρ‚ΠΈΠ΅ (агрСгация) позволяСт ΠΎΡ†Π΅Π½ΠΈΡ‚ΡŒ Π΄Π°Π½Π½Ρ‹Π΅ Π½Π° Π±ΠΎΠ»Π΅Π΅ высоком ΡƒΡ€ΠΎΠ²Π½Π΅ ΠΈ ΠΎΠ±Π½Π°Ρ€ΡƒΠΆΠΈΡ‚ΡŒ Π°Π½ΠΎΠΌΠ°Π»ΠΈΠΈ ΠΈ Ρ‚Π΅Π½Π΄Π΅Π½Ρ†ΠΈΠΈ, ΠΊΠΎΡ‚ΠΎΡ€Ρ‹Π΅ Π½Π΅ Π²ΠΈΠ΄Π½Ρ‹ Π² Π΄Π΅Ρ‚Π°Π»ΡŒΠ½Ρ‹Ρ… ΠΎΡ‚Ρ‡Ρ‘Ρ‚Π°Ρ….

НапримСр, Π² ΠΈΠ·ΠΌΠ΅Ρ€Π΅Π½ΠΈΠ΅ с ΠΊΠΎΠ΄Π°ΠΌΠΈ статуса запросов DIM_HTTP_STATUS Π΄ΠΎΠ±Π°Π²ΠΈΠΌ Π³Ρ€ΡƒΠΏΠΏΡƒ:

STATUS / GROUP
0xx / n.a.
1xx / Informational
2xx / Successful
3xx / Redirection
4xx / Client Error
5xx / Server Error

Π˜Π·ΠΌΠ΅Ρ€Π΅Π½ΠΈΠ΅ ΠΏΠΎΠ»ΡŒΠ·ΠΎΠ²Π°Ρ‚Π΅Π»ΡŒΡΠΊΠΈΡ… Π°Π³Π΅Π½Ρ‚ΠΎΠ² DIM_USER_AGENT Π±ΡƒΠ΄Π΅Ρ‚ ΡΠΎΠ΄Π΅Ρ€ΠΆΠ°Ρ‚ΡŒ Π°Ρ‚Ρ€ΠΈΠ±ΡƒΡ‚Ρ‹ AGENT_OS ΠΈ AGENT_BOT, ΠΎΡ‚Π²Π΅Ρ‡Π°ΡŽΡ‰ΠΈΠ΅ Π·Π° Π³Ρ€ΡƒΠΏΠΏΡ‹. Π˜Ρ… ΠΌΠΎΠΆΠ½ΠΎ Π·Π°ΠΏΠΎΠ»Π½ΡΡ‚ΡŒ Π² процСссС ETL:

Π—Π°Π³Ρ€ΡƒΠ·ΠΊΠ° DIM_USER_AGENT

/* Propagate the user agent from access log */
INSERT INTO DIM_USER_AGENT (USER_AGENT_NK, AGENT_OS, AGENT_ENGINE, AGENT_DEVICE, AGENT_BOT, UPDATE_DT)
WITH CLS AS (
	SELECT BROWSER
	FROM STG_ACCESS_LOG WHERE LENGTH(BROWSER)>1
	GROUP BY BROWSER
)
SELECT
	CLS.BROWSER AS USER_AGENT_NK,
	CASE
	WHEN INSTR(CLS.BROWSER,'Macintosh')>0
		THEN 'Macintosh'
	WHEN INSTR(CLS.BROWSER,'iPhone')>0
			 OR INSTR(CLS.BROWSER,'iPad')>0
			 OR INSTR(CLS.BROWSER,'iPod')>0
			 OR INSTR(CLS.BROWSER,'Apple TV')>0
			 OR INSTR(CLS.BROWSER,'Darwin')>0
		THEN 'iOS'
	WHEN INSTR(CLS.BROWSER,'Android')>0
		THEN 'Android'
	WHEN INSTR(CLS.BROWSER,'X11;')>0 OR INSTR(CLS.BROWSER,'Wayland;')>0 OR INSTR(CLS.BROWSER,'linux-gnu')>0
		THEN 'Linux'
	WHEN INSTR(CLS.BROWSER,'BB10;')>0 OR INSTR(CLS.BROWSER,'BlackBerry')>0
		THEN 'BlackBerry'
	WHEN INSTR(CLS.BROWSER,'Windows')>0
		THEN 'Windows'
	ELSE 'n.a.' END AS AGENT_OS, -- OS
	CASE
	WHEN INSTR(CLS.BROWSER,'AppleCoreMedia')>0
		THEN 'AppleWebKit'
	WHEN INSTR(CLS.BROWSER,') ')>1 AND LENGTH(CLS.BROWSER)>INSTR(CLS.BROWSER,') ')
		THEN COALESCE(SUBSTR(CLS.BROWSER, INSTR(CLS.BROWSER,') ')+2, LENGTH(CLS.BROWSER) - INSTR(CLS.BROWSER,') ')-1), 'N/A')
	ELSE 'n.a.' END AS AGENT_ENGINE, -- Engine
	CASE
	WHEN INSTR(CLS.BROWSER,'iPhone')>0
		THEN 'iPhone'
	WHEN INSTR(CLS.BROWSER,'iPad')>0
		THEN 'iPad'
	WHEN INSTR(CLS.BROWSER,'iPod')>0
		THEN 'iPod'
	WHEN INSTR(CLS.BROWSER,'Apple TV')>0
		THEN 'Apple TV'
	WHEN INSTR(CLS.BROWSER,'Android ')>0 AND INSTR(CLS.BROWSER,'Build')>0
		THEN COALESCE(SUBSTR(CLS.BROWSER, INSTR(CLS.BROWSER,'Android '), INSTR(CLS.BROWSER,'Build')-INSTR(CLS.BROWSER,'Android ')), 'n.a.')
	WHEN INSTR(CLS.BROWSER,'Android ')>0 AND INSTR(CLS.BROWSER,'MIUI')>0
		THEN COALESCE(SUBSTR(CLS.BROWSER, INSTR(CLS.BROWSER,'Android '), INSTR(CLS.BROWSER,'MIUI')-INSTR(CLS.BROWSER,'Android ')), 'n.a.')
	ELSE 'n.a.' END AS AGENT_DEVICE, -- Device
	CASE
	WHEN INSTR(LOWER(CLS.BROWSER),'yandex.com')>0
		THEN 'yandex'
	WHEN INSTR(LOWER(CLS.BROWSER),'googlebot')>0
		THEN 'google'
	WHEN INSTR(LOWER(CLS.BROWSER),'bingbot')>0
		THEN 'microsoft'
	WHEN INSTR(LOWER(CLS.BROWSER),'ahrefsbot')>0
		THEN 'ahrefs'
	WHEN INSTR(LOWER(CLS.BROWSER),'jobboersebot')>0 OR INSTR(LOWER(CLS.BROWSER),'jobkicks')>0
		THEN 'job.de'
	WHEN INSTR(LOWER(CLS.BROWSER),'mail.ru')>0
		THEN 'mail.ru'
	WHEN INSTR(LOWER(CLS.BROWSER),'baiduspider')>0
		THEN 'baidu'
	WHEN INSTR(LOWER(CLS.BROWSER),'mj12bot')>0
		THEN 'majestic-12'
	WHEN INSTR(LOWER(CLS.BROWSER),'duckduckgo')>0
		THEN 'duckduckgo'
	WHEN INSTR(LOWER(CLS.BROWSER),'bytespider')>0
		THEN 'bytespider'
	WHEN INSTR(LOWER(CLS.BROWSER),'360spider')>0
		THEN 'so.360.cn'
	WHEN INSTR(LOWER(CLS.BROWSER),'compatible')>0 OR INSTR(LOWER(CLS.BROWSER),'http')>0
		OR INSTR(LOWER(CLS.BROWSER),'libwww')>0 OR INSTR(LOWER(CLS.BROWSER),'spider')>0
		OR INSTR(LOWER(CLS.BROWSER),'java')>0 OR INSTR(LOWER(CLS.BROWSER),'python')>0
		OR INSTR(LOWER(CLS.BROWSER),'robot')>0 OR INSTR(LOWER(CLS.BROWSER),'curl')>0 OR INSTR(LOWER(CLS.BROWSER),'wget')>0
		THEN 'other'
	ELSE 'n.a.' END AS AGENT_BOT, -- Bot
	STRFTIME('%s','now') AS UPDATE_DT
FROM CLS
LEFT OUTER JOIN DIM_USER_AGENT TRG
ON CLS.BROWSER = TRG.USER_AGENT_NK
WHERE TRG.DIM_USER_AGENT_ID IS NULL

Π˜Π½Ρ‚Π΅Π³Ρ€Π°Ρ†ΠΈΡ Π΄Π°Π½Π½Ρ‹Ρ…

Π’ΠΊΠ»ΡŽΡ‡Π°Π΅Ρ‚ Π² сСбя ΠΎΡ€Π³Π°Π½ΠΈΠ·Π°Ρ†ΠΈΡŽ ΠΏΠ΅Ρ€Π΅Π΄Π°Ρ‡ΠΈ Π΄Π°Π½Π½Ρ‹Ρ… ΠΈΠ· ΠΎΠΏΠ΅Ρ€Π°Ρ†ΠΈΠΎΠ½Π½ΠΎΠΉ систСмы Π² ΠΎΡ‚Ρ‡Ρ‘Ρ‚Π½ΡƒΡŽ. Для этого Π½Π΅ΠΎΠ±Ρ…ΠΎΠ΄ΠΈΠΌΠΎ ΡΠΎΠ·Π΄Π°Ρ‚ΡŒ стСйдТ Ρ‚Π°Π±Π»ΠΈΡ†Ρƒ со структурой, Π°Π½Π°Π»ΠΎΠ³ΠΈΡ‡Π½ΠΎΠΉ источнику.

Π’ стСйдТ информация ΠΎ Π²Π΅Π± страницах ΠΏΠΎΠΏΠ°Π΄Π°Π΅Ρ‚ ΠΈΠ· бэкапа CMS Π² Π²ΠΈΠ΄Π΅ запросов вставки.

Π—Π°Π³Ρ€ΡƒΠ·ΠΊΠ° историчСской Ρ‚Π°Π±Π»ΠΈΡ†Ρ‹ DIM_REQUEST Π±Π°Π·ΠΎΠ²Ρ‹ΠΌΠΈ Π΄Π°Π½Π½Ρ‹ΠΌΠΈ происходит Π² Ρ‚Ρ€ΠΈ шага: Π·Π°Π³Ρ€ΡƒΠ·ΠΊΠ° Π½ΠΎΠ²Ρ‹Ρ… ΠΊΠ»ΡŽΡ‡Π΅ΠΉ ΠΈ Π°Ρ‚Ρ€ΠΈΠ±ΡƒΡ‚ΠΎΠ², ΠΎΠ±Π½ΠΎΠ²Π»Π΅Π½ΠΈΠ΅ ΡΡƒΡ‰Π΅ΡΡ‚Π²ΡƒΡŽΡ‰ΠΈΡ… ΠΈ фиксированиС ΡƒΠ΄Π°Π»Ρ‘Π½Π½Ρ‹Ρ… записСй.

Π—Π°Π³Ρ€ΡƒΠ·ΠΊΠ° Π½ΠΎΠ²Ρ‹Ρ… записСй SCD2

/* Load request table SCD from master data */
INSERT INTO DIM_REQUEST (DIM_REQUEST_ID_HIST, REQUEST_NK, PAGE_TITLE, PAGE_DESCR, PAGE_KEYWORDS, DELETE_FLAG, UPDATE_DT)
WITH CLS  AS ( -- prepare keys
	SELECT
	'/' || NAME AS REQUEST_NK,
	TITLE       AS PAGE_TITLE,
	CASE WHEN DESCRIPTION = '' OR DESCRIPTION IS NULL
	     THEN 'n.a.' ELSE DESCRIPTION
	END AS PAGE_DESCR,
	CASE WHEN KEYWORDS = '' OR KEYWORDS IS NULL
	     THEN 'n.a.' ELSE KEYWORDS
	END AS PAGE_KEYWORDS
	FROM STG_CMS_MENU
	WHERE CONTENT_TYPE != 'folder' -- only web pages
	  AND PAGE_TITLE != 'n.a.' -- master data which make sense
)
/* new records from stage: CLS */
SELECT
	-1 AS DIM_REQUEST_ID_HIST,
	CLS.REQUEST_NK,
	CLS.PAGE_TITLE,
	CLS.PAGE_DESCR,
	CLS.PAGE_KEYWORDS,
	0 AS DELETE_FLAG,
	STRFTIME('%s','now') AS UPDATE_DT
FROM CLS
LEFT OUTER JOIN
 (
	SELECT
	DIM_REQUEST_ID,
	REQUEST_NK,
	PAGE_TITLE,
	PAGE_DESCR,
	PAGE_KEYWORDS
	FROM DIM_REQUEST_V_ACT
) TRG ON CLS.REQUEST_NK = TRG.REQUEST_NK
WHERE TRG.REQUEST_NK IS NULL -- no such record in data mart

ОбновлСниС Π°Ρ‚Ρ€ΠΈΠ±ΡƒΡ‚ΠΎΠ² SCD2

/* Load request table SCD from master data */
INSERT INTO DIM_REQUEST (DIM_REQUEST_ID_HIST, REQUEST_NK, PAGE_TITLE, PAGE_DESCR, PAGE_KEYWORDS, DELETE_FLAG, UPDATE_DT)
WITH CLS  AS ( -- prepare keys
	SELECT
	'/' || NAME AS REQUEST_NK,
	TITLE       AS PAGE_TITLE,
	CASE WHEN DESCRIPTION = '' OR DESCRIPTION IS NULL
	     THEN 'n.a.' ELSE DESCRIPTION
	END AS PAGE_DESCR,
	CASE WHEN KEYWORDS = '' OR KEYWORDS IS NULL
	     THEN 'n.a.' ELSE KEYWORDS
	END AS PAGE_KEYWORDS
	FROM STG_CMS_MENU
	WHERE CONTENT_TYPE != 'folder' -- only web pages
	  AND PAGE_TITLE != 'n.a.' -- master data which make sense
)
/* updated records from stage: CLS and build reference to history: HIST */
SELECT
	HIST.DIM_REQUEST_ID AS DIM_REQUEST_ID_HIST,
	HIST.REQUEST_NK,
	CLS.PAGE_TITLE,
	CLS.PAGE_DESCR,
	CLS.PAGE_KEYWORDS,
	0 AS DELETE_FLAG,
	STRFTIME('%s','now') AS UPDATE_DT
FROM CLS,
     DIM_REQUEST_V_ACT TRG,
     DIM_REQUEST HIST
WHERE CLS.REQUEST_NK = TRG.REQUEST_NK
  AND TRG.DIM_REQUEST_ID = HIST.DIM_REQUEST_ID
  AND ( CLS.PAGE_TITLE != HIST.PAGE_TITLE /* changes only */
     OR CLS.PAGE_DESCR != HIST.PAGE_DESCR
     OR CLS.PAGE_KEYWORDS != HIST.PAGE_KEYWORDS )

Π£Π΄Π°Π»Ρ‘Π½Π½Ρ‹Π΅ записи SCD2

/* Load request table SCD from master data */
INSERT INTO DIM_REQUEST (DIM_REQUEST_ID_HIST, REQUEST_NK, PAGE_TITLE, PAGE_DESCR, PAGE_KEYWORDS, DELETE_FLAG, UPDATE_DT)
WITH CLS  AS ( -- prepare keys
	SELECT
	'/' || NAME AS REQUEST_NK,
	TITLE       AS PAGE_TITLE
	FROM STG_CMS_MENU
	WHERE CONTENT_TYPE != 'folder' -- only web pages
	  AND PAGE_TITLE != 'n.a.' -- master data which make sense
)
/*  deleted records in data mart: TRG */
SELECT
	TRG.DIM_REQUEST_ID AS DIM_REQUEST_ID_HIST,
	TRG.REQUEST_NK,
	TRG.PAGE_TITLE,
	TRG.PAGE_DESCR,
	TRG.PAGE_KEYWORDS,
	1 AS DELETE_FLAG,
	STRFTIME('%s','now') AS UPDATE_DT
FROM (
	SELECT
	DIM_REQUEST_ID,
	REQUEST_NK,
	PAGE_TITLE,
	PAGE_DESCR,
	PAGE_KEYWORDS
	FROM DIM_REQUEST_V_ACT
	WHERE PAGE_TITLE != 'n.a.' -- track master data only
	  AND DELETE_FLAG = 0 -- not already deleted
) TRG
LEFT OUTER JOIN CLS ON TRG.REQUEST_NK = CLS.REQUEST_NK
WHERE CLS.REQUEST_NK IS NULL -- no such record in stage

ΠšΠ°ΠΆΠ΄Ρ‹ΠΉ источник Π΄Π°Π½Π½Ρ‹Ρ… Π½Π΅ΠΎΠ±Ρ…ΠΎΠ΄ΠΈΠΌΠΎ ΡΠΎΠΏΡ€ΠΎΠ²ΠΎΠ΄ΠΈΡ‚ΡŒ Ρ„ΠΎΡ€ΠΌΠ°Π»ΡŒΠ½Ρ‹ΠΌ описаниСм, Π½Π°ΠΏΡ€ΠΈΠΌΠ΅Ρ€, Π² Ρ„Π°ΠΉΠ»Π΅ readme.txt:

ΠŸΠΎΠ»ΡƒΡ‡Π°Ρ‚Π΅Π»ΡŒ Π΄Π°Π½Π½Ρ‹Ρ… Ρ„ΠΎΡ€ΠΌΠ°Π»ΡŒΠ½ΠΎ/тСхничСски: имя, элСктронный адрСс
ΠŸΠΎΡΡ‚Π°Π²Ρ‰ΠΈΠΊ Π΄Π°Π½Π½Ρ‹Ρ… Ρ„ΠΎΡ€ΠΌΠ°Π»ΡŒΠ½ΠΎ/тСхничСски: имя, элСктронный адрСс
Π˜ΡΡ‚ΠΎΡ‡Π½ΠΈΠΊ Π΄Π°Π½Π½Ρ‹Ρ…: ΠΏΡƒΡ‚ΡŒ ΠΊ Ρ„Π°ΠΉΠ»Ρƒ, названия сСрвисов
Π˜Π½Ρ„ΠΎΡ€ΠΌΠ°Ρ†ΠΈΡ ΠΎ доступС ΠΊ Π΄Π°Π½Π½Ρ‹ΠΌ: ΠΏΠΎΠ»ΡŒΠ·ΠΎΠ²Π°Ρ‚Π΅Π»ΠΈ ΠΈ ΠΏΠ°Ρ€ΠΎΠ»ΠΈ

Π‘Ρ…Π΅ΠΌΠ° двиТСния Π΄Π°Π½Π½Ρ‹Ρ… ΠΏΠΎΠΌΠΎΠΆΠ΅Ρ‚ Π² процСссС сопровоТдСния ΠΈ обновлСния, Π½Π°ΠΏΡ€ΠΈΠΌΠ΅Ρ€, Π² тСкстовом Π²ΠΈΠ΄Π΅:

ΠŸΠ΅Ρ€Π΅ΠΌΠ΅Ρ‰Π΅Π½ΠΈΠ΅ Ρ„Π°ΠΉΠ»Π°. Π˜ΡΡ‚ΠΎΡ‡Π½ΠΈΠΊ: ftp.domain.net: /logs/access.log ЦСль: /var/www/access.log
Π§Ρ‚Π΅Π½ΠΈΠ΅ Π² стСйдТ. ЦСль: STG_ACCESS_LOG
Π—Π°Π³Ρ€ΡƒΠ·ΠΊΠ° ΠΈ трансформация. ЦСль: FCT_ACCESS_REQUEST_REF_HH
Π—Π°Π³Ρ€ΡƒΠ·ΠΊΠ° ΠΈ трансформация. ЦСль: FCT_ACCESS_USER_AGENT_DD
ΠžΡ‚Ρ‡Ρ‘Ρ‚. ЦСль: /var/www/report.html

Π’Ρ‹Π²ΠΎΠ΄

Π’Π°ΠΊΠΈΠΌ ΠΎΠ±Ρ€Π°Π·ΠΎΠΌ, ΡΡ‚Π°Ρ‚ΡŒΡ описываСт Ρ‚Π°ΠΊΠΈΠ΅ ΠΌΠ΅Ρ…Π°Π½ΠΈΠ·ΠΌΡ‹, ΠΊΠ°ΠΊ интСграция Π±Π°Π·ΠΎΠ²Ρ‹Ρ… Π΄Π°Π½Π½Ρ‹Ρ… ΠΈ Π²Π²Π΅Π΄Π΅Π½ΠΈΠ΅ Π½ΠΎΠ²Ρ‹Ρ… ΡƒΡ€ΠΎΠ²Π½Π΅ΠΉ Π°Π³Ρ€Π΅Π³Π°Ρ†ΠΈΠΈ. Они Π½ΡƒΠΆΠ½Ρ‹ ΠΏΡ€ΠΈ построСнии Ρ…Ρ€Π°Π½ΠΈΠ»ΠΈΡ‰ Π΄Π°Π½Π½Ρ‹Ρ… с Ρ†Π΅Π»ΡŒΡŽ получСния Π΄ΠΎΠΏΠΎΠ»Π½ΠΈΡ‚Π΅Π»ΡŒΠ½Ρ‹Ρ… Π·Π½Π°Π½ΠΈΠΉ ΠΈ ΡƒΠ»ΡƒΡ‡ΡˆΠ΅Π½ΠΈΡ качСства ΠΈΠ½Ρ„ΠΎΡ€ΠΌΠ°Ρ†ΠΈΠΈ.

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

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