2016-01-11 100 views
4

我們有一個分析產品。對於我們的每個客戶,我們提供一個JavaScript代碼,他們將這些代碼放入他們的網站。如果用戶訪問我們的客戶站點,Java腳本代碼會打到我們的服務器,以便我們代表此客戶存儲此頁面訪問。每個客戶都包含唯一的域名。MySql - 處理表格大小和性能

我們正在MySql表中存儲此頁訪問。

以下是表格模式。

CREATE TABLE `page_visits` (
    `domain` varchar(50) DEFAULT NULL, 
    `guid` varchar(100) DEFAULT NULL, 
    `sid` varchar(100) DEFAULT NULL, 
    `url` varchar(2500) DEFAULT NULL, 
    `ip` varchar(20) DEFAULT NULL, 
    `is_new` varchar(20) DEFAULT NULL, 
    `ref` varchar(2500) DEFAULT NULL, 
    `user_agent` varchar(255) DEFAULT NULL, 
    `stats_time` datetime DEFAULT NULL, 
    `country` varchar(50) DEFAULT NULL, 
    `region` varchar(50) DEFAULT NULL, 
    `city` varchar(50) DEFAULT NULL, 
    `city_lat_long` varchar(50) DEFAULT NULL, 
    `email` varchar(100) DEFAULT NULL, 
    KEY `sid_index` (`sid`) USING BTREE, 
    KEY `domain_index` (`domain`), 
    KEY `email_index` (`email`), 
    KEY `stats_time_index` (`stats_time`), 
    KEY `domain_statstime` (`domain`,`stats_time`), 
    KEY `domain_email` (`domain`,`email`) 
) ENGINE=InnoDB DEFAULT CHARSET=utf8 | 

我們沒有此表的主鍵。

MySQL服務器的細節

這是谷歌雲的MySQL(版本5.6)和存儲容量10TB是。

截至目前,我們的桌子上有3.5億行,桌子的尺寸是300 GB。即使一個客戶與另一個客戶之間沒有關係,我們仍將所有客戶詳細信息存儲在同一個表中。

問題1:對於少數客戶在表中有大量行的情況,對這些客戶的查詢性能非常低。

實例查詢1:

SELECT count(DISTINCT sid) AS count,count(sid) AS total FROM page_views WHERE domain = 'aaa' AND stats_time BETWEEN CONVERT_TZ('2015-02-05 00:00:00','+05:30','+00:00') AND CONVERT_TZ('2016-01-01 23:59:59','+05:30','+00:00'); 
+---------+---------+ 
| count | total | 
+---------+---------+ 
| 1056546 | 2713729 | 
+---------+---------+ 
1 row in set (13 min 19.71 sec) 

我會在這裏更新更多的查詢。我們需要5-10秒以內的結果,有可能嗎?

問題2:表格大小正在迅速增加,我們可能會在今年年底達到5 TB的表格大小,所以我們想要分割表格。我們希望在一臺機器上保留與一個客戶有關的所有記錄。這個分片的最佳實踐是什麼?

我們正在考慮針對上述問題的方法,請爲我​​們提供解決這些問題的最佳實踐。

爲每個客戶

1)如果我們爲每個客戶建立單獨的表有什麼優點和缺點創建單獨的表。截至目前,我們有3萬個客戶,到今年年底可能達到10萬,這意味着數據庫中有10萬個表。我們同時訪問所有表以進行讀取和寫入。

2)我們會去同一個表中,將創建基於日期範圍

UPDATE分區:一個「客戶」由域確定的? 答案是肯定的

感謝

回答

1

首先,如果過大的數據類型批判:

`domain` varchar(50) DEFAULT NULL, -- normalize to MEDIUMINT UNSIGNED (3 bytes) 
    `guid` varchar(100) DEFAULT NULL, -- what is this for? 
    `sid` varchar(100) DEFAULT NULL, -- varchar? 
    `url` varchar(2500) DEFAULT NULL, 
    `ip` varchar(20) DEFAULT NULL, -- too big for IPv4, too small for IPv6; see below 
    `is_new` varchar(20) DEFAULT NULL, -- flag? Consider `TINYINT` or `ENUM` 
    `ref` varchar(2500) DEFAULT NULL, 
    `user_agent` varchar(255) DEFAULT NULL, -- normalize! (add new rows as new agents are created) 
    `stats_time` datetime DEFAULT NULL, 
    `country` varchar(50) DEFAULT NULL, -- use standard 2-letter code (see below) 
    `region` varchar(50) DEFAULT NULL, -- see below 
    `city` varchar(50) DEFAULT NULL, -- see below 
    `city_lat_long` varchar(50) DEFAULT NULL, -- unusable in current format; toss? 
    `email` varchar(100) DEFAULT NULL, 

對於IP地址,使用inet6_aton(),然後存儲在BINARY(16)

對於country,請使用CHAR(2) CHARACTER SET ascii - 只有2個字節。

國家+地區+城市+(也許)latlng - 將此標準化爲「位置」。

所有這些更改可能會將磁盤佔用空間減半。更小 - >更多可緩存 - >更少I/O - >更快。

其他問題 ...

,大大加快您的sid櫃檯,改變

KEY `domain_statstime` (`domain`,`stats_time`), 

KEY dss (domain_id,`stats_time`, sid), 

這將是一個 「覆蓋索引」,因此深得不必在指數和數據之間反彈2713729倍 - 反彈是13分鐘的成本。 (domain_id在下面討論。)

這是多餘的與上述索引,DROP它: KEY domain_indexdomain

是一個 「客戶」,由domain確定?

每個InnoDB表必須有一個PRIMARY KEY。有3種方式獲得PK;你選擇了'最差' - 一個由引擎編寫的隱藏的6字節整數。我認爲從某些列組合中沒有「自然」的PK可用?然後,需要明確的BIGINT UNSIGNED。 (是的,這是8個字節,但各種形式的維護需要一個明確的PK

如果查詢包括WHERE domain = '...',那麼我建議如下。 (這將大大提高所有這樣的查詢。)

id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT, 
domain_id MEDIUMINT UNSIGNED NOT NULL, -- normalized to `Domains` 
PRIMARY KEY(domain_id, id), -- clustering on customer gives you the speedup 
INDEX(id) -- this keeps AUTO_INCREMENT happy 

推薦你看看pt-online-schema-change作出所有這些變化。但是,我不知道如果沒有明確的PRIMARY KEY它可以工作。

「每個客戶的獨立表」? 。這是一個常見的問題;答案是肯定的,我不會重複所有沒有100K桌子的原因。

拆分

「分片」 跨多個分割數據

要進行分片,您需要在代碼的某處查看domain並確定哪個服務器將處理該查詢,然後將其傳遞出去。當你有寫入縮放問題時,建議分片。你沒有提到,所以目前還不清楚分片是否可取。

當像domain(或domain_id)分片,您可以使用(1)哈希挑服務器,(2),字典查找(100K的行),或(3)的混合體。

我喜歡混合哈希值,比如說1024個值,然後查看1024行的表來查看哪臺機器有數據。由於添加新的碎片並將用戶遷移到不同的碎片是主要的任務,我認爲這種混合是一種合理的折衷方案。查找表需要分發給所有將動作重定向到碎片的客戶端。

如果您的'寫作'正在枯竭,請參閱high speed ingestion以瞭解如何加快速度。

分區

PARTITIONing跨多個分割數據 「子表」。

只有limited number of use cases其中分區會爲您購買任何性能。你沒有表明任何適用於你的用例。閱讀該博客,看看您是否認爲該分區可能有用。

您提到了「按日期範圍分區」。大多數查詢是否包含日期範圍?如果是這樣,這種分區可能是是可取的。 (請參閱上面的鏈接以獲得最佳做法。)想到其他一些選項:

計劃A:PRIMARY KEY(domain_id, stats_time, id)但這樣龐大,需要在每個二級索引上增加更多開銷。 (每個二級索引默默地包括PK的所有列。)

計劃B:讓stats_time包含微秒,然後調整這些值以避免發生爭吵。然後使用stats_time而不是id。但是這需要一些額外的複雜性,特別是如果有多個客戶端插入數據。 (如果需要,我可以詳細說明)。

計劃C:有一個將stats_time值映射到ID的表。在進行真正的查詢之前查找id範圍,然後使用WHERE id BETWEEN ... AND stats_time ...。 (同樣,亂碼。)

彙總表

很多不同日期範圍計算的東西形式的疑問?建議基於每小時的摘要表。 More discussion

COUNT(DISTINCT sid)特別難以摺疊成彙總表。例如,每個小時的唯一計數不能加在一起以獲得當天的唯一計數。但是我也有一個technique

+0

@詹姆斯感謝您的詳細解釋,你能分享任何解釋爲什麼100k表不是一個好的決定。 – Rams

+0

真棒迴應。豐富的信息。 – cloudpre

+0

100K表是這個論壇上的常見問題。以下是一些反對它的論點:操作系統開銷和放緩;代碼中的複雜性;很少優勢。可能會有很多小桌子和一些大桌子 - 每個極端都有它自己的低效率。 –

0

我不會做,如果我是你。首先想到的是,在接收到瀏覽量消息時,我將消息發送到隊列中,以便工作人員稍後可以拾取並插入到數據庫中(也可以批量填充);我也增加redis中siteid:date的計數器(例如)。在sql中做count對於這種情況只是一個壞主意。

+0

@Tran嘿感謝您的回答,您是否無論如何在MySql上做到這一點 – Rams

+0

@RANS你想在SQL中執行它嗎? –

+0

@我們希望在Google Cloud SQL等雲上提供的任何解決方案上做到這一點。 – Rams