一区二区三区三上|欧美在线视频五区|国产午夜无码在线观看视频|亚洲国产裸体网站|无码成年人影视|亚洲AV亚洲AV|成人开心激情五月|欧美性爱内射视频|超碰人人干人人上|一区二区无码三区亚洲人区久久精品

您好,歡迎來(lái)電子發(fā)燒友網(wǎng)! ,新用戶?[免費(fèi)注冊(cè)]

您的位置:電子發(fā)燒友網(wǎng)>源碼下載>數(shù)值算法/人工智能>

如何優(yōu)化數(shù)據(jù)庫(kù)負(fù)載

大小:0.5 MB 人氣: 2017-09-28 需要積分:2

  摘要:一個(gè)前端開(kāi)發(fā)者介紹了他和他的數(shù)據(jù)庫(kù)朋友們是如何降低基于Ruby網(wǎng)站數(shù)據(jù)庫(kù)負(fù)載的故事。以下為譯文:

  數(shù)據(jù)庫(kù)負(fù)載可能是個(gè)沉默的性能殺手。我一直都在優(yōu)化我的一個(gè)網(wǎng)站應(yīng)用,用來(lái)吸引人們參與到開(kāi)放代碼社區(qū),但我注意到一些隨機(jī)的查詢時(shí)間異常,有時(shí)會(huì)長(zhǎng)達(dá)15s或更長(zhǎng)。雖然我注意到這個(gè)現(xiàn)象有些時(shí)候了,我直到最近才開(kāi)始優(yōu)化我的數(shù)據(jù)庫(kù)查詢。我首先通過(guò)建立索引優(yōu)化了我的主頁(yè)(并且使用Rack Mini Profiler工具),然后我追蹤并刪除掉了一些代價(jià)高昂的查詢。在這些重要的提升后,平均響應(yīng)時(shí)間在50ms左右,95%在1s以內(nèi)。但是,我遇到一個(gè)討厭的問(wèn)題,在24小時(shí)內(nèi),95%響應(yīng)時(shí)間可能急升到15s或30s并在短時(shí)間內(nèi)超時(shí)。本文將介紹我如何查找并解決這個(gè)問(wèn)題。這最終使我的數(shù)據(jù)庫(kù)降低了80%的負(fù)載。

  這是我的響應(yīng)時(shí)間圖,我希望移除這些異常峰值。

  為了理解為什么這個(gè)(或這些)請(qǐng)求是如此的慢,我用了計(jì)量工具。在本例中,我使用了Heroku Scout 插件。我修改了比例來(lái)展示過(guò)去12小時(shí)內(nèi)的請(qǐng)求(默認(rèn)是3小時(shí))。然后聚焦到這些巨大的峰值。這是我看到的

  應(yīng)用或者數(shù)據(jù)庫(kù)肯定有些不對(duì)勁。在scout的輸出里,你可以看到一個(gè)查詢要38秒才能完成。我試著手工去訪問(wèn)這個(gè)頁(yè)面但是它很快就加載了。所以不會(huì)是頁(yè)面的問(wèn)題。

  很幸運(yùn)的是我在Heroku工作,我立即在我們數(shù)據(jù)庫(kù)工程師的Slack聊天室里問(wèn)他們是什么可能的原因引起了性能的下降。他們問(wèn)我數(shù)據(jù)庫(kù)的平均負(fù)載。我用的是一個(gè)standard-o 數(shù)據(jù)庫(kù)Heroku聲稱它可以承受0.2 負(fù)載。我打開(kāi)了Papertrail 日志 并尋找 load-avg。 我在那條慢請(qǐng)求時(shí)間附件發(fā)現(xiàn)這條記錄

  Jun 29 01:01:01 issuetriage app/heroku-postgres: source=DATABASE sample#current_transaction=271694354

  sample#db_size=4469950648bytes sample#tables=14 sample#active-connections=35

  sample#waiting-connections=0 sample#index-cache-hit-rate=0.87073 sample#table-cache-hit-rate=0.47657

  sample#load-avg-1m=2.15 sample#load-avg-5m=1.635 sample#load-avg-15m=0.915

  sample#read-iops=16.325 sample#write-iops=0 sample#memory-total=15664468kB

  sample#memory-free=255628kB sample#memory-cached=14213308kB sample#memory-postgres=549408kB

  一般負(fù)載在0.2或以下是正常的,但我的應(yīng)用峰值到了2.15,呦呵!

  我已經(jīng)花了不少時(shí)間來(lái)優(yōu)化我的查詢時(shí)間,所以我對(duì)此還是很意外的。一位數(shù)據(jù)工程師建議我使用 pg:outliers 命令(Heroku pg:extra CLI 擴(kuò)展)

  如果你不使用Heroku,你可以通過(guò) _pg_stat_statements_ 表來(lái)得到同樣的數(shù)據(jù)

  當(dāng)我安裝了這個(gè)擴(kuò)展并使用該命令我發(fā)現(xiàn)一條查詢語(yǔ)句占了高達(dá)(你猜對(duì)了)80%的執(zhí)行時(shí)間。

  $ heroku pg:outliers

  total_exec_time | prop_exec_time | ncalls | sync_io_time | query

  ------------------+----------------+-------------+------------------+------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

  3790:50:52.62102 | 80.2% | 100,727,265 | 727:08:40.969477 | SELECT ? AS one FROM “repos” WHERE LOWER(“repos”?!皀ame”) = LOWER($1) AND (“repos”?!癷d” != $2) AND “repos”?!皍ser_name” = $3 LIMIT $4

  493:04:18.903353 | 10.4% | 101,625,003 | 52:09:48.599802 | SELECT COUNT(*) FROM “issues” WHERE “issues”?!皉epo_id” = $1 AND “issues”?!皊tate” = $2

  這是那個(gè)查詢語(yǔ)句(方便較小的顯示屏幕)

  SELECT ?

  AS one

  FROM “repos”

  WHERE LOWER(“repos”?!皀ame”) = LOWER($1) AND

  (“repos”?!癷d” != $2) AND

  “repos”?!皍ser_name” = $3

  LIMIT $4

  對(duì)此我感到很奇怪。因?yàn)槲也挥浀梦覍?xiě)過(guò)這樣的查詢語(yǔ)句。我搜索了我的代碼庫(kù)中含有 LOWER SQL函數(shù)的代碼但沒(méi)有找到任何一條。于是我求助于Papertrail來(lái)看看在現(xiàn)實(shí)生產(chǎn)環(huán)境中這個(gè)語(yǔ)句什么時(shí)候被調(diào)用的。我找到的第一條記錄在一個(gè)創(chuàng)建操作中:

  Started POST “/repos” for 131.228.216.131 at 2017-06-29 09:34:59

  Processing by ReposController#create as HTML

  Parameters: {“utf8”=》“?”, “authenticity_token”=》lIR3ayNog==“, ”url“=》”https://github.com/styleguidist/react-

  User Load (0.9ms) SELECT “users”.* FROM “users” WHERE “users”。

  Repo Load (1.1ms) SELECT “repos”.* FROM “repos” WHERE “repos”。

  (0.9ms) BEGIN

  Repo Exists (1.9ms) SELECT 1 AS one FROM “repos” WHERE LOWER( $3 LIMIT $4

 ?。?.5ms) COMMIT

 ?。?.8ms) BEGIN

  RepoSubion Exists (4.3ms) SELECT 1 AS one FROM “repo_ns”?!皍ser_id” = $2 LIMIT $3

  SQL (5.6ms) INSERT INTO “repo_subions” (“created_at”,

 ?。?.1ms) COMMIT

 ?。跘ctiveJob] Enqueued SendSingleTriageEmailJob (Job ID: cbe2b04a-d271

  Redirected to https://www.codetriage.com/styleguidist/react-

  Completed 302 Found in 39ms (ActiveRecord: 21.9ms)

  Jun 29 02:35:00 issuetriage heroku/router: at=info method=POST path=“/repos” host=www.codetriage.com request_id=5e706722-7668-4980-ab5e-9a9853feffc9 fwd=“131.228.216.131” dyno=web.3 connect=1ms service=542ms status=302 bytes=1224 protocol=https

  為了簡(jiǎn)潔,日志的標(biāo)簽被移除了

  這有點(diǎn)難讀,但你可以看 Repo Exists右邊的查詢語(yǔ)句。我查看了那個(gè)控制入口函數(shù)(ReposController#create)并檢查了一些可疑方法,但是結(jié)果都沒(méi)問(wèn)題(例如,都沒(méi)有調(diào)用 SQL LOWER 函數(shù))。那么問(wèn)題來(lái)了,這些查詢語(yǔ)句是從哪里來(lái)的呢?

  最終答案是來(lái)自于我的數(shù)據(jù)模型中的這一行代碼。這行貌似無(wú)害的代碼承擔(dān)了我數(shù)據(jù)庫(kù)80%的負(fù)載。這個(gè) Validate 調(diào)用是 Rails 試圖保證兩個(gè) Repo 記錄沒(méi)有相同的用戶名和用戶姓名。它沒(méi)有采用在數(shù)據(jù)庫(kù)中強(qiáng)制執(zhí)行一致性,而是在模型對(duì)象上加了一個(gè) before commit的鉤子,這樣在模型對(duì)象寫(xiě)入數(shù)據(jù)庫(kù)前,它會(huì)查詢數(shù)據(jù)庫(kù)來(lái)確保我們創(chuàng)建一個(gè)新 repo 記錄的時(shí)候沒(méi)有重復(fù)的記錄。

  在我寫(xiě)這個(gè)驗(yàn)證邏輯的時(shí)候我沒(méi)有想太多。看這個(gè)驗(yàn)證代碼本身也很難相信它居然引發(fā)如此大的數(shù)據(jù)庫(kù)負(fù)載。畢竟我只有大概2000條repo記錄。理論上這個(gè)驗(yàn)證調(diào)用最多調(diào)用2000次,對(duì)吧?

  為了回答這個(gè)問(wèn)題,我重新查找日志并找到另外一處這個(gè)SQL語(yǔ)句執(zhí)行的地方。

  Jun 29 07:00:32 issuetriage app/scheduler.8183: [ActiveJob] Enqueued PopulateIssuesJob (Job ID: 9e04e63f-a515-4dcd-947f-0f777e56dd1b) to Sidekiq(default) with arguments: #《GlobalID:0x00000004f98a68 @uri=#《URI::GID gid://code-triage/Repo/1008》》

  Performing PopulateIssuesJob (uri=#《URI::GID gid://code-

  User Load (10.4ms) SELECT

 ?。?5.4ms) BEGIN

  Repo Exists (352.9ms) SELECT $3 LIMIT $4

  SQL (3.7ms) UPDATE “repos”

 ?。?.5ms) COMMIT

  Performed PopulatessuesJob (Job ID: 9e04e63f-a515-4dcd-947f-0f777e56dd1b) from Sidekiq(default) in 629.22ms

  為了簡(jiǎn)潔,日志的標(biāo)簽被移除了

  這一次這個(gè)查詢語(yǔ)句不是來(lái)自網(wǎng)頁(yè)動(dòng)作,而是一個(gè)后臺(tái)作業(yè)。當(dāng)我檢查它時(shí)我意識(shí)到這個(gè)驗(yàn)證不止在創(chuàng)建時(shí)執(zhí)行,它還在_任何_記錄的修改時(shí)執(zhí)行。即使用戶名或用戶姓名沒(méi)有改動(dòng),它還是會(huì)查詢數(shù)據(jù)庫(kù)來(lái)確保沒(méi)有重復(fù)。

  我有一個(gè)晚間任務(wù)來(lái)遍歷所有的代碼庫(kù)并且有時(shí)會(huì)更新他們的記錄。事實(shí)是我的后臺(tái)任務(wù)和這個(gè)慢網(wǎng)絡(luò)請(qǐng)求發(fā)生在幾乎相同的時(shí)間?;旧?,我自己就是那個(gè)討厭的鄰居。我自己的后臺(tái)任務(wù)使得數(shù)據(jù)庫(kù)負(fù)載急升,遠(yuǎn)超一般負(fù)載容量。其他普通的對(duì)時(shí)間敏感的網(wǎng)絡(luò)請(qǐng)求就因?yàn)闆](méi)有數(shù)據(jù)CPU時(shí)間而被迫等待并超時(shí)。

  我立刻刪除了這個(gè)驗(yàn)證并用一個(gè)單一索引代替,同時(shí)在數(shù)據(jù)庫(kù)上加了限制。

  class AddUniqueIndexToRepos 《 ActiveRecord::Migration[5.1]

  def change

  add_index :repos, [:name, :user_name], :unique =》 true

  end

  end

  現(xiàn)在我們可以確定在數(shù)據(jù)庫(kù)里沒(méi)有兩個(gè)記錄會(huì)有相同的用戶名/用戶名字組合,Rails程序也不需要在每次修改記錄時(shí)去查詢數(shù)據(jù)庫(kù)。

  更不用提Rails程序驗(yàn)證存在競(jìng)爭(zhēng)并且實(shí)際上并不保證一致性,最好是在數(shù)據(jù)庫(kù)層面確保這些(一致性)事情。

  你可能注意到 SQL LOWER 函數(shù)并沒(méi)有在我的單一性索引中出現(xiàn)。在我的應(yīng)用中,我已經(jīng)對(duì)存儲(chǔ)的數(shù)據(jù)做了規(guī)范化處理,所以這個(gè)邏輯是多余的。

  在刪除驗(yàn)證代碼并增加單一性索引后,我的應(yīng)用再也沒(méi)有出現(xiàn)過(guò)30秒以上的請(qǐng)求延時(shí)。數(shù)據(jù)庫(kù)一直都在0.2 load-avg 或以下 運(yùn)行。

  當(dāng)我們面對(duì)數(shù)據(jù)庫(kù)運(yùn)行變慢時(shí),我們傾向于考慮一個(gè)單獨(dú)的查詢語(yǔ)句的性能。我們很少考慮一個(gè)或幾個(gè)查詢語(yǔ)句可能相互影響并拖慢整個(gè)網(wǎng)站。

  在看到 pg:outliers 結(jié)果后,我可以在其他幾個(gè)合適的位置加上索引來(lái)減少負(fù)載。 例如:

  issuetriage::DATABASE=》 EXPLAIN ANALYZE SELECT “repos”.* FROM “repos” WHERE “repos”。”full_name” = ‘schneems/wicked’ LIMIT 1;

  QUERY PLAN

  Limit (cost=0.00..39297.60 rows=1 width=1585) (actual time=57.885..57.885 rows=1 loops=1)

  -》 Seq Scan on repos (cost=0.00..39297.60 rows=1 width=1585) (actual time=57.884..57.884 rows=1 loops=1)

  Filter: ((full_name)::text = ‘schneems/wicked’::text)

  Rows Removed by Filter: 823

  Total runtime: 57.912 ms

 ?。? rows)

  這里整體執(zhí)行時(shí)間并不是在幾秒內(nèi),這個(gè)并不算好。那個(gè)串行化的掃描很快,但并非沒(méi)有代價(jià)。我對(duì) _full_name 加了一個(gè)索引,現(xiàn)在它快的要飛起來(lái)。同樣的查詢可以在 1ms 內(nèi)返回。針對(duì)這些調(diào)用的所以也幫助我減少了數(shù)據(jù)庫(kù)負(fù)載。

  總結(jié)一下:

  一個(gè)高的 load-avg 會(huì)拖慢所有的查詢語(yǔ)句,不僅僅是那些慢查詢語(yǔ)句。

  使用pg:outlier 來(lái)發(fā)現(xiàn)那些占用了更多CPU時(shí)間的查詢語(yǔ)句(如果你使用Heroko),如果你使用其他平臺(tái),你也可以使用 _pg_stat_statements

  使用日志來(lái)定位查詢語(yǔ)句發(fā)生的時(shí)間并用 EXPLAIN ANALYZE 來(lái)分析為什么一個(gè)查詢?nèi)绱撕臅r(shí)。

  你的查詢語(yǔ)句的輸入很重要并且可能?chē)?yán)重影響到查詢性能

  添加索引,改變數(shù)據(jù)的存儲(chǔ)或者改變程序邏輯來(lái)避免異常的查詢

  如果可能的話,利用數(shù)據(jù)庫(kù)來(lái)保證數(shù)據(jù)一致性而不是使用程序代碼

  事后來(lái)看,這是個(gè)很簡(jiǎn)單的錯(cuò)誤并且很容易定位和修復(fù),只是要花點(diǎn)時(shí)間和使用正確的工具。我注意到那個(gè)30s+的請(qǐng)求延時(shí)峰值有幾個(gè)月了,甚至幾年。我從沒(méi)有想去深挖原因,因?yàn)槲以詾檫@會(huì)很麻煩。它也只是每天發(fā)生一次,對(duì)用戶的影響很小。利用正確的工具和我們數(shù)據(jù)庫(kù)工程師的建議,我很快就解決了。我不認(rèn)為我掌握了數(shù)據(jù)庫(kù)優(yōu)化,但至少現(xiàn)在我達(dá)到了我的目標(biāo)。謝謝你閱讀我的數(shù)據(jù)庫(kù)負(fù)載之旅。

非常好我支持^.^

(0) 0%

不好我反對(duì)

(0) 0%

如何優(yōu)化數(shù)據(jù)庫(kù)負(fù)載下載

相關(guān)電子資料下載

      發(fā)表評(píng)論

      用戶評(píng)論
      評(píng)價(jià):好評(píng)中評(píng)差評(píng)

      發(fā)表評(píng)論,獲取積分! 請(qǐng)遵守相關(guān)規(guī)定!

      ?