ある SQL 調査の物語

昚幎の XNUMX 月に、VWO サポヌト チヌムから興味深いバグ レポヌトを受け取りたした。 倧䌁業顧客向けの分析レポヌトの XNUMX ぀では、読み蟌み時間が法倖に長いず思われたした。 そしお、これは私の責任分野であるため、私はすぐに問題の解決に集䞭したした。

背景

私が䜕に぀いお話しおいるのかを明確にするために、VWO に぀いお少し説明したす。 これは、Web サむト䞊でタヌゲットを絞ったさたざたなキャンペヌンを開始できるプラットフォヌムです。A/B 実隓の実斜、蚪問者ずコンバヌゞョンの远跡、販売目暙到達プロセスの分析、ヒヌト マップの衚瀺、蚪問蚘録の再生などを行いたす。

しかし、このプラットフォヌムで最も重芁なこずはレポヌトです。 䞊蚘の機胜はすべお盞互に接続されおいたす。 たた、䌁業顧客にずっお、膚倧な量の情報は、それを分析圢匏で衚瀺する匷力なプラットフォヌムがなければたったく圹に立ちたせん。

プラットフォヌムを䜿甚するず、倧芏暡なデヌタ セットに察しおランダムなク゚リを䜜成できたす。 簡単な䟋を次に瀺したす。

Chrome を䜿甚したナヌザヌ、たたは (ペヌロッパに居䜏し、iPhone を䜿甚したナヌザヌ) の、<日付 d1> から <日付 d2> たでのペヌゞ「abc.com」のすべおのクリックを衚瀺したす。

ブヌル挔算子に泚意しおください。 クラむアントはク゚リ むンタヌフェむスでこれらを䜿甚しお、任意の耇雑なク゚リを䜜成しおサンプルを取埗できたす。

リク゚ストが遅い

問題のクラむアントは、盎感的にすぐに機胜するはずの䜕かを行おうずしおいたした。

「/jobs」を含む URL を持぀ペヌゞにアクセスしたナヌザヌのすべおのセッション レコヌドを衚瀺したす

このサむトには倧量のトラフィックがあり、このサむトだけで XNUMX 䞇を超える䞀意の URL が保存されおいたした。 そしお、圌らは自分たちのビゞネス モデルに関連した非垞にシンプルな URL テンプレヌトを芋぀けたいず考えおいたした。

予備調査

デヌタベヌス内で䜕が起こっおいるのかを芋おみたしょう。 以䞋は元の遅い SQL ク゚リです。

SELECT 
    count(*) 
FROM 
    acc_{account_id}.urls as recordings_urls, 
    acc_{account_id}.recording_data as recording_data, 
    acc_{account_id}.sessions as sessions 
WHERE 
    recording_data.usp_id = sessions.usp_id 
    AND sessions.referrer_id = recordings_urls.id 
    AND  (  urls &&  array(select id from acc_{account_id}.urls where url  ILIKE  '%enterprise_customer.com/jobs%')::text[]   ) 
    AND r_time > to_timestamp(1542585600) 
    AND r_time < to_timestamp(1545177599) 
    AND recording_data.duration >=5 
    AND recording_data.num_of_pages > 0 ;

そしお、タむミングは次のずおりです。

蚈画時間: 1.480 ミリ秒 実行時間: 1431924.650 ミリ秒

ク゚リは 150 䞇行をクロヌルしたした。 ク゚リ プランナヌはいく぀かの興味深い詳现を瀺したしたが、明らかなボトルネックはありたせんでした。

リク゚ストをさらに詳しく調べおみたしょう。 ご芧のずおり、圌はそうしたす JOIN XNUMX ぀のテヌブル:

  1. セッション: ブラりザ、ナヌザヌ ゚ヌゞェント、囜などのセッション情報を衚瀺したす。
  2. 録音デヌタ: 蚘録された URL、ペヌゞ、蚪問時間
  3. URLは: 非垞に倧きな URL の重耇を避けるため、URL は別のテヌブルに保存されたす。

たた、すべおのテヌブルがすでにパヌティション化されおいるこずに泚意しおください。 account_id。 こうするこずで、XNUMX ぀の特に倧芏暡なアカりントが他のアカりントに問題を匕き起こす状況が排陀されたす。

手がかりを探しおいたす

詳しく調べるず、特定のリク゚ストに䜕か問題があるこずがわかりたす。 この行を詳しく芋おみる䟡倀がありたす。

urls && array(
	select id from acc_{account_id}.urls 
	where url  ILIKE  '%enterprise_customer.com/jobs%'
)::text[]

最初に考えたのは、おそらく次のような理由からです。 ILIKE これらすべおの長い URL (1,4 䞇以䞊の URL がありたす) 独特の ã“のアカりント甚に収集された URL) のパフォヌマンスが䜎䞋する可胜性がありたす。

しかし、いいえ、それが重芁ではありたせん!

SELECT id FROM urls WHERE url ILIKE '%enterprise_customer.com/jobs%';
  id
--------
 ...
(198661 rows)

Time: 5231.765 ms

テンプレヌト怜玢リク゚スト自䜓には 5 秒しかかかりたせん。 XNUMX 䞇件の䞀意の URL からパタヌンを怜玢するこずは明らかに問題ありたせん。

リスト䞊の次の容疑者は数名です JOIN。 おそらく、䜿いすぎが速床䜎䞋の原因になっおいるのではないでしょうか? い぀もの JOINはパフォヌマンスの問題の最も明癜な候補ですが、私たちのケヌスが兞型的であるずは信じおいたせんでした。

analytics_db=# SELECT
    count(*)
FROM
    acc_{account_id}.urls as recordings_urls,
    acc_{account_id}.recording_data_0 as recording_data,
    acc_{account_id}.sessions_0 as sessions
WHERE
    recording_data.usp_id = sessions.usp_id
    AND sessions.referrer_id = recordings_urls.id
    AND r_time > to_timestamp(1542585600)
    AND r_time < to_timestamp(1545177599)
    AND recording_data.duration >=5
    AND recording_data.num_of_pages > 0 ;
 count
-------
  8086
(1 row)

Time: 147.851 ms

そしお、これは私たちの堎合でもありたせんでした。 JOINはかなり速いこずが刀明したした。

容疑者の範囲を絞り蟌む

可胜な限りパフォヌマンスを向䞊させるために、ク゚リの倉曎を開始する準備ができおいたした。 私のチヌムず私は 2 ぀の䞻なアむデアを開発したした。

  • サブク゚リ URL には EXISTS を䜿甚したす: URL のサブク゚リに問題がないかをもう䞀床確認したいず思いたした。 これを実珟する XNUMX ぀の方法は、単に次のようにするこずです。 EXISTS. EXISTS 猶 条件に䞀臎する唯䞀の文字列が芋぀かるずすぐに終了するため、パフォヌマンスが倧幅に向䞊したす。

SELECT
	count(*) 
FROM 
    acc_{account_id}.urls as recordings_urls,
    acc_{account_id}.recording_data as recording_data,
    acc_{account_id}.sessions as sessions
WHERE
    recording_data.usp_id = sessions.usp_id
    AND  (  1 = 1  )
    AND sessions.referrer_id = recordings_urls.id
    AND  (exists(select id from acc_{account_id}.urls where url  ILIKE '%enterprise_customer.com/jobs%'))
    AND r_time > to_timestamp(1547585600)
    AND r_time < to_timestamp(1549177599)
    AND recording_data.duration >=5
    AND recording_data.num_of_pages > 0 ;
 count
 32519
(1 row)
Time: 1636.637 ms

はい、そうです。 ラップされた堎合のサブク゚リ EXISTS、すべおが超高速になりたす。 次の論理的な疑問は、なぜ次のようなリク゚ストが行われるのかずいうこずです。 JOIN-ami ずサブク゚リ自䜓は個別には速いですが、䞀緒にするず非垞に遅くなりたすか?

  • サブク゚リを CTE に移動する : ク゚リ自䜓が高速な堎合は、最初に高速な結果を単玔に蚈算しおから、それをメむン ク゚リに提䟛できたす。

WITH matching_urls AS (
    select id::text from acc_{account_id}.urls where url  ILIKE  '%enterprise_customer.com/jobs%'
)

SELECT 
    count(*) FROM acc_{account_id}.urls as recordings_urls, 
    acc_{account_id}.recording_data as recording_data, 
    acc_{account_id}.sessions as sessions,
    matching_urls
WHERE 
    recording_data.usp_id = sessions.usp_id 
    AND  (  1 = 1  )  
    AND sessions.referrer_id = recordings_urls.id
    AND (urls && array(SELECT id from matching_urls)::text[])
    AND r_time > to_timestamp(1542585600) 
    AND r_time < to_timestamp(1545107599)
    AND recording_data.duration >=5 
    AND recording_data.num_of_pages > 0;

しかし、それでも非垞に遅かったです。

犯人を芋぀ける

その間ずっず、䞀぀の小さなこずが私の目の前にちら぀きたしたが、私はそれを垞に払いのけおいたした。 しかし、他に䜕も残っおいないので、私も圌女を芋るこずにしたした。 私が話しおいるのは && オペレヌタヌ。 さよなら EXISTS パフォヌマンスが向䞊しただけ && これは、䜎速ク゚リのすべおのバヌゞョンにわたっお残っおいる唯䞀の共通因子でした。

芋お ドキュメンテヌション、それがわかりたす && XNUMX ぀の配列間で共通の芁玠を芋぀ける必芁がある堎合に䜿甚されたす。

元のリク゚ストでは次のようになりたす。

AND  (  urls &&  array(select id from acc_{account_id}.urls where url  ILIKE  '%enterprise_customer.com/jobs%')::text[]   )

぀たり、URL に察しおパタヌン怜玢を実行し、共通の投皿を持぀すべおの URL ずの共通郚分を芋぀けたす。 ここでの「urls」は、すべおの URL を含むテヌブルを指すのではなく、テヌブル内の「urls」列を指すため、これは少し混乱したす。 recording_data.

に察する疑惑が高たる䞭、 &&、生成されたク゚リプランでそれらの確認を芋぀けようずしたした EXPLAIN ANALYZE (プランはすでに保存されおいたすが、ク゚リ プランナヌの䞍透明性を理解しようずするよりも、SQL を詊しおみる方が気が楜です)。

Filter: ((urls && ($0)::text[]) AND (r_time > '2018-12-17 12:17:23+00'::timestamp with time zone) AND (r_time < '2018-12-18 23:59:59+00'::timestamp with time zone) AND (duration >= '5'::double precision) AND (num_of_pages > 0))
                           Rows Removed by Filter: 52710

からのみフィルタヌが数行ありたした &&。 ぀たり、この操䜜は費甚がかかるだけでなく、耇数回実行されるこずになりたす。

条件を分離しおこれをテストしたした

SELECT 1
FROM 
    acc_{account_id}.urls as recordings_urls, 
    acc_{account_id}.recording_data_30 as recording_data_30, 
    acc_{account_id}.sessions_30 as sessions_30 
WHERE 
	urls &&  array(select id from acc_{account_id}.urls where url  ILIKE  '%enterprise_customer.com/jobs%')::text[]

このク゚リは遅かったです。 なぜなら JOIN-s もサブク゚リも高速です。残っおいるのは && オペレヌタヌ。

これは単なるキヌ操䜜です。 パタヌンを怜玢するには、基になる URL テヌブル党䜓を垞に怜玢する必芁があり、垞に亀差郚分を芋぀ける必芁がありたす。 これらは単なる ID を参照しおいるため、URL レコヌドで盎接怜玢するこずはできたせん。 urls.

解決ぞの道のり

&& どちらのセットも巚倧なので遅いです。 亀換するず動䜜が比范的早くなりたす urls Ма { "http://google.com/", "http://wingify.com/" }.

を䜿甚せずにPostgresで亀差を蚭定する方法を探し始めたした。 &&、しかしあたり成功しおいたせん。

結局、私たちは問題を単独で解決するこずに決めたした。すべおを私にください urls URL がパタヌンに䞀臎する行。 远加の条件がなければ、次のようになりたす - 

SELECT urls.url
FROM 
	acc_{account_id}.urls as urls,
	(SELECT unnest(recording_data.urls) AS id) AS unrolled_urls
WHERE
	urls.id = unrolled_urls.id AND
	urls.url  ILIKE  '%jobs%'

代わりに JOIN 構文はサブク゚リを䜿甚しお展開しただけです recording_data.urls 配列に条件を盎接適甚できるようにする WHERE.

ここで最も重芁なこずは、 && 指定された゚ントリに䞀臎する URL が含たれおいるかどうかを確認するために䜿甚されたす。 少し目を现めるず、この操䜜が配列の芁玠 (たたはテヌブルの行) を移動し、条件 (䞀臎) が満たされたずきに停止するこずがわかりたす。 䜕か思い出したせんか うん、 EXISTS.

それ以来 recording_data.urls サブク゚リ コンテキストの倖偎から参照できるため、これが発生した堎合は、叀い友人に頌るこずができたす。 EXISTS そしおサブク゚リをそれでラップしたす。

すべおをたずめるず、最終的に最適化されたク゚リが埗られたす。

SELECT 
    count(*) 
FROM 
    acc_{account_id}.urls as recordings_urls, 
    acc_{account_id}.recording_data as recording_data, 
    acc_{account_id}.sessions as sessions 
WHERE 
    recording_data.usp_id = sessions.usp_id 
    AND  (  1 = 1  )  
    AND sessions.referrer_id = recordings_urls.id 
    AND r_time > to_timestamp(1542585600) 
    AND r_time < to_timestamp(1545177599) 
    AND recording_data.duration >=5 
    AND recording_data.num_of_pages > 0
    AND EXISTS(
        SELECT urls.url
        FROM 
            acc_{account_id}.urls as urls,
            (SELECT unnest(urls) AS rec_url_id FROM acc_{account_id}.recording_data) 
            AS unrolled_urls
        WHERE
            urls.id = unrolled_urls.rec_url_id AND
            urls.url  ILIKE  '%enterprise_customer.com/jobs%'
    );

そしお最終的なリヌドタむムは Time: 1898.717 ms お祝いの時間ですか

そんなに早くない たず、正しさを確認する必芁がありたす。 非垞に疑問に思っおいたのですが、 EXISTS ロゞックを倉曎しおより早く完了するように最適化したす。 明らかではない゚ラヌがリク゚ストに远加されおいないこずを確認する必芁がありたす。

簡単なテストを実行するこずでした count(*) 倚数の異なるデヌタセットに察する䜎速ク゚リず高速ク゚リの䞡方で。 次に、デヌタの小さなサブセットに぀いお、すべおの結果が正しいこずを手動で怜蚌したした。

すべおのテストで䞀貫しお肯定的な結果が埗られたした。 すべおを修正したした

孊んだ教蚓

この話からは、次のような倚くの教蚓が埗られたす。

  1. ク゚リ プランはすべおを物語るわけではありたせんが、手がかりを提䟛するこずはできたす。
  2. 䞻な容疑者が真犯人ずは限らない
  3. 遅いク゚リを分解しおボトルネックを特定できる
  4. すべおの最適化が本質的に還元的であるわけではありたせん
  5. 䜿甚 EXIST可胜であれば、生産性の劇的な向䞊に぀ながる可胜性がありたす

出力

ク゚リ時間は玄 24 分から 2 秒になり、パフォヌマンスが倧幅に向䞊したした。 この蚘事は倧々的に掲茉されたしたが、私たちが行ったすべおの実隓は 1,5 日で行われ、最適化ずテストには 2  XNUMX 時間かかったず掚定されおいたす。

SQL は、恐れるこずがなければ玠晎らしい蚀語ですが、孊習しお䜿甚するように努めおください。 SQL ク゚リがどのように実行されるか、デヌタベヌスがク゚リ プランを生成する方法、むンデックスがどのように機胜するか、そしお単玔に扱うデヌタのサむズをよく理解するこずで、ク゚リの最適化を成功させるこずができたす。 ただし、さたざたなアプロヌチを詊し続け、ボトルネックを芋぀けお問題をゆっくりず解決するこずも同様に重芁です。

このような結果を達成する最も優れた点は、目に芋える速床の向䞊です。以前は読み蟌たれなかったレポヌトが、ほが瞬時に読み蟌たれるようになりたした。

特に感謝したす ç§ã®åŒå¿—たち ã‚¢ãƒ‡ã‚£ãƒ†ã‚£ãƒ€ãƒ»ãƒŸã‚·ãƒ¥ãƒ©ã®å‘œä»€ã§ã‚¢ãƒ‡ã‚£ãƒ†ã‚£ãƒ€ ガりル Ðž ãƒŽã‚¡ãƒ«ãƒ³ãƒ»ãƒžãƒ«ãƒ›ãƒˆãƒ© ãƒ–レヌンストヌミング甚ず ãƒ‡ã‚£ãƒ³ã‚«ãƒŒãƒ«ãƒ»ãƒ‘ンディル æœ€çµ‚リク゚ストに別れを告げる前に、最埌のリク゚ストで重芁な間違いを芋぀けおくれおありがずう!

出所 habr.com

コメントを远加したす