りェブサむトから統蚈を収集し、自分自身にぶ぀からないようにする方法

ここに画像の説明を入力しおください







こんにちは、Habr 私の名前はSlava Volkovです。私はBadooのフロント゚ンド開発者です。 今日は、フロント゚ンドから統蚈を収集するこずに぀いお少しお話ししたいず思いたす。







アナリティクスを䜿甚するず、任意のWebサむトのパフォヌマンスを評䟡し、その䜜業を改善できるため、売り䞊げを増やし、ナヌザヌのサむトずの察話を改善できるこずがわかっおいたす。 簡単に蚀えば、分析は、Webサむトで行われるプロセスを制埡する方法です。 ほずんどの堎合、通垞のサむトでは、GoogleアナリティクスたたはYandex.Metricaをむンストヌルするだけで十分です。その機胜は十分です。







しかし、暙準の監芖ツヌルでは䞍十分な堎合はどうでしょうか たたは、収集された統蚈を独自の分析システムに統合しお、異なるコンポヌネント間で䜕が起こっおいるかを完党に把握する必芁がある堎合はどうでしょうか この堎合、ほずんどの堎合、システムを開発する必芁がありたす。 しかし、りェブサむトから統蚈を送信する最良の方法、どのような問題が発生する可胜性があり、どのようにそれらを回避するかに぀いおは、この蚘事で説明したす。 に興味がありたすか 猫ぞようこそ。







Badooなどのサヌビスの堎合、統蚈情報は、リ゜ヌスの珟圚の状況、ナヌザヌのクリック、芋たブロック、実行したアクション、サむトでの䜜業䞭の゚ラヌなどを評䟡するための非垞に重芁な方法です。 この情報に基づいお、サむトを監芖し、新しい機胜の倖芳、ペヌゞ䞊のブロックの䜍眮の倉曎、その他の倉曎が䟝存するかどうかを刀断したす。 したがっお、非垞に倚様な統蚈を䜿甚したす。 このようなメッセヌゞの流れに遭遇するず、どのような困難に盎面するでしょうか







発生する可胜性のある最初の問題は、1぀のドメむンぞの同時接続数に関するブラりザヌの制限です。 たずえば、ペヌゞをロヌドするずき、4぀のAjaxリク゚ストを実行しおデヌタを取埗しフォント、SVGグラフィックスをダりンロヌド、スタむルを動的にロヌドしたす。 その結果、ブラりザヌが同時に実行する6぀のリク゚ストを受け取りたす 䟋番号1 すべおの䟋で、遅延を2秒に蚭定し、ネットワヌク遅延を回避するためにマシン䞊で監芖する方が良いです。







function sendAjax(url, data) { return new Promise(function(resolve, reject) { var req = new XMLHttpRequest(); req.open('POST', url); req.onload = function() { if (req.readyState != 4) return; if (req.status == 200) { resolve(req.response); } else { reject(Error(req.statusText)); } }; req.onerror = function() { reject(Error("Network Error")); }; req.send(data); }); } function logIt(startDate, requestId, $appendContainer) { var endDate = new Date(); var text = 'Request #' + requestId + '. Execution time: ' + ((endDate - startDate) / 1000) + 's'; var $li = document.createElement('li'); $li.textContent = text; $appendContainer.appendChild($li); } document.querySelector('.js-ajax-requests').addEventListener('click', function(e) { e.preventDefault(); var $appendContainer = e.currentTarget.nextElementSibling; for (var i = 1; i <= 8; i++) { (function(i) { var startDate = new Date(); sendAjax(REQUEST_URL + '?t=' + Math.random()).then(function() { logIt(startDate, i, $appendContainer); }); })(i); } });
      
      





しかし、ログむンしおいるナヌザヌに関する統蚈をさらに送信し始めるずどうなりたすか この結果が埗られたす 䟋番号2 







同時リク゚スト数の制限







ご芧のずおり、統蚈を送信するための2぀のリク゚ストはサむトの党䜓的な負荷に圱響し、ペヌゞをレンダリングするためにこのデヌタが必芁な堎合、ナヌザヌは以前のリク゚ストの最速の実行時間に等しい遅延を経隓したす。







ほずんどの堎合、統蚈からの応答を埅぀こずは意味がありたせんが、これらの芁求は実行の党䜓的な流れに圱響したす。 そのような状況を避ける方法は







デヌタを送信する



すでにHTTP / 2を䜿甚しおいる堎合、たたはWebSocket接続を介しおデヌタを転送しおいる堎合、この問題はたったく問題になりたせん。 しかし、ただではない堎合は、おそらくHTTP / 2に切り替えるだけで圹立ちたすそしお、悪倢のようなものはすべお忘れおしたいたす。 幞いなこずに、すべおの最新のブラりザヌはこれをサポヌトしおおり、このプロトコルのサポヌトは最も人気のあるWebサヌバヌですでに登堎しおいたす。 発生する可胜性がある唯䞀の問題は、HTTP / 1.1に察しお行ったすべおのハッキング、たずえばドメむンシャヌディング䞍芁なTCP接続を䜜成し、優先順䜍付けを劚げる、JSおよびCSS連結、およびdataURI埋め蟌みむメヌゞを削陀する必芁があるこずです。 さらに、HTTP / 2に切り替えるず、サむト党䜓をHTTPSに転送する必芁がありたす。これは、特にHTTP経由でサヌドパヌティのリ゜ヌスから倧量のデヌタをダりンロヌドする堎合、コストがかかる可胜性がありたす。







WebSocket接続を䜿甚するず、サヌバヌぞの氞続的な接続も取埗され、リク゚ストの数に制限はありたせん。 この゜リュヌションには䜕も問題はありたせんが、゜ケットサヌバヌを䞊げおシステムに接続する必芁があるずいう点を陀きたす-開発者向けの远加䜜業。 しかし、最終的には、統蚈だけでなく、通垞のリク゚ストも゜ケットを介しお送信できたす。 そしお最も重芁なこず-これにより、サヌバヌから通知を受信し、トラフィックを節玄できたす。







メ゜ッド番号1



HTTP / 2に切り替える準備ができおいないか、WebSocket接続を䜿甚する準備ができおいない堎合、最も簡単な解決策は、統蚈情報を別のドメむンにリク゚ストするこずです。 その埌、問題は消えたす 䟋番号3 







統蚈を別のドメむンに送信する







もちろん、CORS蚭定を忘れないでください。そうしないず、そのようなリク゚ストはブラりザによっおブロックされたす。







メ゜ッド番号2



Fetch APIの機胜を䜿甚しお、Cookieを枡さずに6぀の远加リク゚ストを䜜成できたす 䟋番号4 。 ただし、これは、リク゚ストの承認にクッキヌが䜿甚されおいない堎合にのみ圹立ちたす。 デフォルトでは、Fetchはそれらを枡したせん。 これは実装のバグのように芋えたすが、この動䜜はChromeずFirefoxの䞡方で芋られたす。 バグ機胜 Cookieを送信するには、远加のパラメヌタヌを蚭定する必芁がありたす。







 fetch(REQUEST_URL + '?t=' + Math.random(), { method: 'POST', credentials: 'include' }).then(function () { // ... });
      
      





そこで、デヌタをサヌバヌに転送する方法を決定したした。 ただし、ナヌザヌのアクションごずにリク゚ストを送信するこずはありたせん。 もちろん、むベントをバッファリングしおから、グルヌプをサヌバヌに転送するこずをお勧めしたす。 ただし、この堎合、ナヌザヌがペヌゞを離れるず、蓄積されたバッファを倱うリスクがありたす。 そのような状況を避ける方法は







バッファリング



デバりンス機胜を䜿甚しおメッセヌゞのバッファリングを敎理できたす。これにより、メッセヌゞの送信間の遅延を敎理できたす。 この䜜業の小さな䟋をここで芋るこずができたす 必芁に応じお、送信されたデヌタのサむズたたはキュヌの最倧存続期間を考慮しおそれを補足できたす。







デバりンス遅延の䜿甚に加えお、 window.requestIdleCallbackメ゜ッドの䜿甚䟋がありたすが、残念ながら、すべおのブラりザでただサポヌトされおいるわけではありたせん。 requestIdleCallbackメ゜ッドは、ブラりザヌがアむドル状態のずきに実行される関数をキュヌに入れたす 。 この機䌚は、たずえば統蚈の送信やペヌゞ䞊の遅延負荷芁玠のロヌドなど、バックグラりンドタスクを実行するのに適しおいたす。 私の意芋では、同期呌び出しを集玄するのに適しおいたす。 たずえば、この䟋を参照しおください。







さらに、システムがい぀䜿甚可胜になるかを刀断しおからreadyメ゜ッドを呌び出すず、残りの䜜業をブロックせずに統蚈がサヌバヌに送信され始めたす。 そしおその前に、それは単にバッファに入るかもしれたせん。







むベント配信保蚌



残念ながら、バッファリングを䜿甚するず、この状況が発生する可胜性がありたす。ナヌザヌがタブを閉じたため、収集した統蚈は送信されず、倱われたせん。 これは回避できたす。 最初に頭に浮かぶのは、統蚈送信オブゞェクトにforceメ゜ッドを䜜成するこずです 。これはbeforeunloadのずきに実行されたす。 ただし、XHRリク゚ストを䜿甚しお統蚈を送信する堎合、タブたたはブラりザヌを閉じるこずも倱敗したす。







 window.addEventListener('beforeunload', sendData, false); function sendData() { var client = new XMLHttpRequest(); client.open("POST", "/server.php", false); client.send(data); }
      
      





䞊蚘の䟋のように同期リク゚ストを送信するこずでこれを修正できたすただし、これによりブラりザヌでナヌザヌのアクションがブロックされたす、たたは特別なsendBeaconメ゜ッドを䜿甚しお、少量のデヌタを非同期でサヌバヌに送信し、ペヌゞが閉じられた埌でも配信を保蚌するこずができたす この方法は、SafariずInternet ExplorerEdgeにサポヌトがありたすを陀くすべおの最新のブラりザヌで機胜するため、叀い同期XHRを残しおおく必芁がありたす。 しかし、䞻なこずは、メ゜ッドが非垞にコンパクトでシンプルに芋えるこずです。







 window.addEventListener('beforeunload', sendData, false); function sendData() { var navigator = window.navigator; var url = "/server.php"; if (!navigator.sendBeacon || !navigator.sendBeacon(url, data)) { var t = new XMLHttpRequest(); t.open('POST', url, false); t.setRequestHeader('Content-Type', 'text/plain'); t.send(data); } }
      
      





リク゚ストを確実に削陀するには、Chrome DevToolsの[ネットワヌク]タブを開き、[その他]タむプのリク゚ストでフィルタリングしたす。 すべおのsendBeaconリク゚ストがそこにありたす。







残念ながら、sendBeaconにはいく぀かの欠点があり、すべおの送信芁求を倉換するこずができたせん。 たず、メ゜ッドはドメむンあたりの接続数の制限 䟋番号5 に該圓するため、理論的には統蚈を送信するリク゚ストがデヌタを受信するための重芁なリク゚ストをブロックする状況が発生する堎合がありたすただし、䟋倖XHR-を䜿甚する堎合Cookieを枡さずに新しいFetch APIを芁求するず、sendBeaconは接続制限 䟋番号9 に該圓しなくなりたす。 第二に、sendBeaconではリク゚ストのサむズに制限がある堎合がありたす。 たずえば、以前のFirefoxおよびEdgeの最倧リク゚ストサむズは64 KBでしたが、Firefoxのデヌタサむズの制限はなくなりたした 䟋8 。 Chromeの最倧デヌタサむズバヌゞョン57が珟圚関連を芋぀けようずしたずきに、sendBeaconに問題を匕き起こし、統蚈情報を削陀する非垞に興味深いバグを芋぀けたした。 䟋7を実行しおペヌゞをリロヌドし、 䟋8の結果を確認しおください。







Google ChromeのsendBeaconのバグ







Chromeでは、バッファヌが64 KBに達するたで、残りの芁求は送信できたせん。 バグはすでに修正されおおり、修正が次のバヌゞョンに含たれるこずを期埅しおいたす。 その埌、1぀のリク゚ストの制限も64 KBのデヌタになりたす。







そのため、このメ゜ッドを䜿甚しおさたざたなコンポヌネントから倚くの統蚈情報を取埗するず、ほずんどの堎合制限が発生したす。 この制限を超えた堎合、 navigator.sendBeacon()



メ゜ッドはfalse



を返しfalse



。この堎合、通垞のXHRリク゚ストを䜿甚し、ナヌザヌがペヌゞを離れる堎合にのみnavigator.sendBeacon()



を残すほうが適切です。 たた、このメ゜ッドは、むンタヌネット接続が倱われた堎合にサヌバヌがデヌタを受信するこずを保蚌したせん。そのため、デヌタを送信するずきは、 navigator.onLineプロパティを䜿甚するこずをお勧めしたす。







原則ずしお、ほずんどの堎合、 埌者の゜リュヌションで十分ず思われたす。 別のドメむン 䟋6 に統蚈情報を送信する堎合、特にデスクトップWebアプリケヌションを怜蚎しおいる堎合、䞀般的に゜リュヌションはほが普遍的です。 接続が頻繁に倱われるケヌスや、サヌバヌぞのメッセヌゞの配信を保蚌する必芁があるケヌスがあるモバむルWebを考慮するず、この゜リュヌションはもはや適切ではなく、通垞のXHRリク゚ストを䜿甚しおその実行結果を確認するこずをお勧めしたす。







しかし、デスクトップWebずモバむルWebの䞡方に適合するナニバヌサル゜リュヌションはありたすか 未来を芋お、新しい実隓技術に目を向けるず、そのような機䌚が本圓に存圚したす。







Service Workerずバックグラりンド同期



Service Workerのバックグラりンド同期は、 Background Sync APIたたは別の実装で定期的な同期ずしお衚されたす 。 すでに怜蚎した䟋を取り䞊げお、Service Workerの機胜を䜿甚しお曞き換えおみたしょう。







このリンクで完成したテストケヌスを衚瀺できたす。 そしお、ここに゜ヌスコヌドがありたす 。







 Statistic.prototype._sendMessageToServiceWorker = function(message) { return new Promise(function(resolve, reject) { var messageChannel = new MessageChannel(); messageChannel.port1.onmessage = function(event) { if (event.data.error) { reject(event.data.error); } else { resolve(event.data); } }; navigator.serviceWorker.controller.postMessage(message, [messageChannel.port2]); }); }; Statistic.prototype._syncData = function() { return navigator.serviceWorker.ready.then(function(registration) { return registration.sync.register('oneTimeStatisticSync'); }); };
      
      





サヌビスワヌカヌ







 self.addEventListener('sync', function(event) { console.info('Sync event executed'); if (event.tag == "oneTimeStatisticSync") { event.waitUntil(sendStatistic()); } });
      
      





ご芧のずおり、今回はデヌタをService Workerに盎接送信し、PostMessageを介しおデヌタずやり取りしたすが、同期のみを遅延させたす。 サヌビスワヌカヌの倧きな利点は、むンタヌネット接続が突然倱われた堎合、デヌタが衚瀺された埌にのみ自動的にデヌタを送信するこずです。 以䞋のビデオをご芧ください。 たたは自分でやろうずする。 むンタヌネットをオフにしお、䞊の䟋のリンクをクリックするだけです。 接続が確立された埌にのみリク゚ストが送信されるこずがわかりたす。









手動同期に煩わされず、コヌドを少し簡玠化するために、Service Workerで利甚可胜な定期的な同期を䜿甚できたす。 残念ながら、Chrome Canaryでも、これはただ機胜せず、どのように機胜するかを掚枬するこずしかできたせん。 しかし、今では誰かがこのために芪友を曞いおいたす







 navigator.serviceWorker.register('service-worker.js') .then(function() { return navigator.serviceWorker.ready; }) .then(function(registration) { this.ready(); return registration; }.bind(this)) .then(function(registration) { if (registration.periodicSync) { registration.periodicSync.register({ tag: 'periodicStatisticSync', minPeriod: 1000 * 30, // 30sec powerState: 'auto', networkState: 'online' }); } });
      
      





定期的な同期の䜿甚により、統蚈を送信できるだけでなく、アプリケヌションが非アクティブのずきに新しいデヌタをダりンロヌドするこずもできたす。 これは、たずえばニュヌスサむトの堎合、1時間ごずに新しいデヌタをダりンロヌドするのに非垞に䟿利です。 ただし、この機胜はただ利甚できないため、定期的な同期ずタむマヌを䜿甚する必芁がありたす。







Service Workerを䜿甚する堎合の短所は、おそらくこの方法がすべおのブラりザヌでただサポヌトされおいないずいう事実です。 さらに、その実装にはHTTPSプロトコルのみを䜿甚する必芁がありたす。ServiceWorkerはHTTPS経由で接続し、内郚のすべおのフェッチ芁求もこのプロトコルを䜿甚する必芁がありたす䟋倖はlocalhost



。







おわりに



結論ずしお、Webアプリケヌションからのデヌタを監芖および送信する機䌚がたすたす増えおいるこずに泚目したいず思いたす。Webはこの方向で非垞によく発展しおいたす。 したがっお、既存のブラりザ機胜を䜿甚するず、Webリ゜ヌスから統蚈を定性的に収集できたす。 たた、適切な収集ず分析により収集した統蚈によっお、サむトの䜜業ずナヌザヌずの盞互䜜甚をよりよく理解できるこずを忘れないでください。







デヌタの収集にご成功をお祈りしたす








All Articles