Como armazenar melhor os ngrams do Google em um banco de dados?

9

Eu baixei onegrams do Google há alguns dias e já é uma quantidade enorme de dados. Inseri o primeiro dos 10 pacotes no mysql e agora tenho um banco de dados de 47 milhões de registros.

Eu estou querendo saber como alguém deve armazenar ngrams do Google em um banco de dados melhor. Quero dizer, se você não estiver usando onegrams, mas, por exemplo, dois ou três gramas, a quantidade será muito maior. Posso armazenar 500 milhões de registros em um banco de dados e trabalhar com ele ou devo dividi-lo em tabelas diferentes?

Depois de quantos registros deve-se dividi-lo e como deve-se dividir melhor (considerando que os twogramas têm 100 arquivos e, portanto, provavelmente cerca de 5 bilhões de registros)? É recomendável usar o particionamento horizontal do MySQL ou criar o próprio particionamento (por exemplo, através do primeiro caractere da palavra => twograms_a).

RolandoMySQLDBA
fonte

Respostas:

4

Havia tantas mudanças que eu teria que fazer na minha primeira resposta, eu começo essa !!!

USE test
DROP TABLE IF EXISTS ngram_key;
DROP TABLE IF EXISTS ngram_rec;
DROP TABLE IF EXISTS ngram_blk;
CREATE TABLE ngram_key
(
    NGRAM_ID UNSIGNED BIGINT NOT NULL AUTO_INCREMENT,
    NGRAM VARCHAR(64) NOT NULL,
    PRIMARY KEY (NGRAM),
    KEY (NGRAM_ID)
) ENGINE=MyISAM ROW_FORMAT=FIXED PARTITION BY KEY(NGRAM) PARTITIONS 256;
CREATE TABLE ngram_rec
(
    NGRAM_ID UNSIGNED BIGINT NOT NULL,
    YR SMALLINT NOT NULL,
    MC SMALLINT NOT NULL,
    PC SMALLINT NOT NULL,
    VC SMALLINT NOT NULL,
    PRIMARY KEY (NGRAM_ID,YR)
) ENGINE=MyISAM ROW_FORMAT=FIXED;
CREATE TABLE ngram_blk
(
    NGRAM VARCHAR(64) NOT NULL,
    YR SMALLINT NOT NULL,
    MC SMALLINT NOT NULL,
    PC SMALLINT NOT NULL,
    VC SMALLINT NOT NULL
) ENGINE=BLACKHOLE;
DELIMITER $$
CREATE TRIGGER populate_ngram AFTER INSERT ON ngram_blk FOR EACH ROW
BEGIN
    DECLARE NEW_ID BIGINT;

    INSERT IGNORE INTO ngram_key (NGRAM) VALUES (NEW.NGRAM);
    SELECT NGRAM_ID INTO NEW_ID FROM ngram_key WHERE NGRAM=NEW.NGRAM;
    INSERT IGNORE INTO ngram_rec VALUES (NEW_ID,NEW.YR,NEW.MC,NEW.PC,NEW.VC);
END; $$
DELIMITER ;
INSERT INTO ngram_blk VALUES
('rolando',1965,31,29,85),
('pamela',1971,33,21,86),
('dominique',1996,30,18,87),
('diamond',1998,13,28,88),
('rolando edwards',1965,31,29,85),
('pamela edwards',1971,33,21,86),
('dominique edwards',1996,30,18,87),
('diamond edwards',1998,13,28,88),
('rolando angel edwards',1965,31,29,85),
('pamela claricia edwards',1971,33,21,86),
('dominique sharlisee edwards',1996,30,18,87),
('diamond ashley edwards',1998,13,28,88);
UPDATE ngram_rec SET yr=yr+1,mc=mc+30,pc=pc+30,vc=vc+30;
INSERT INTO ngram_blk VALUES
('rolando',1965,31,29,85),
('pamela',1971,33,21,86),
('dominique',1996,30,18,87),
('diamond',1998,13,28,88),
('rolando edwards',1965,31,29,85),
('pamela edwards',1971,33,21,86),
('dominique edwards',1996,30,18,87),
('diamond edwards',1998,13,28,88),
('rolando angel edwards',1965,31,29,85),
('pamela claricia edwards',1971,33,21,86),
('dominique sharlisee edwards',1996,30,18,87),
('diamond ashley edwards',1998,13,28,88);
UPDATE ngram_rec SET yr=yr+1,mc=mc+30,pc=pc+30;
INSERT INTO ngram_blk VALUES
('rolando',1965,31,29,85),
('pamela',1971,33,21,86),
('dominique',1996,30,18,87),
('diamond',1998,13,28,88),
('rolando edwards',1965,31,29,85),
('pamela edwards',1971,33,21,86),
('dominique edwards',1996,30,18,87),
('diamond edwards',1998,13,28,88),
('rolando angel edwards',1965,31,29,85),
('pamela claricia edwards',1971,33,21,86),
('dominique sharlisee edwards',1996,30,18,87),
('diamond ashley edwards',1998,13,28,88);
UPDATE ngram_rec SET yr=yr+1,mc=mc+30;
SELECT * FROM ngram_key;
SELECT * FROM ngram_rec;
SELECT A.ngram NGram,B.yr Year,B.mc Matches,B.pc Pages,B.vc Volumes FROM 
ngram_key A,ngram_rec B
WHERE A.ngram='rolando angel edwards'
AND A.ngram_id=B.ngram_id;

Tabelas muito menores para informações do ano, mas teclas muito maiores para preservar o ngram original. Também aumentei a quantidade de dados de teste. Você pode recortar e colar isso diretamente no MySQL.

EMBARGO

Simplesmente remova o ROW_FORMAT e ele se tornará manímico e comprima as tabelas ngram_key muito menores.


Métricas do DiskSpace

nrgram_rec possui 17 bytes por linha,
8 bytes para ngram_id (valor máximo não assinado 18446744073709551615 [2 ^ 64 - 1])
8 bytes para 4 smallints (2 bytes cada)
flag de exclusão interna MyISAM de 1 byte

Entrada de índice para ngram_rec = 10 bytes (8 (ngram_id) + 2 (ano))

47 milhões de linhas X 17 bytes por linha = 0799 milhões de bytes = 761.98577 MB
47 milhões de linhas X 12 bytes por linha = 0564 milhões de bytes = 537,85231 MB
47 milhões de linhas X 29 bytes por linha = 1363 milhões de bytes = 1.269393 GB

5 bilhões de linhas X 17 bytes por linha = 085 bilhões de bytes = 079.1624 GB
5 bilhões de linhas X 12 bytes por linha = 060 bilhões de bytes = 055.8793 GB
5 bilhões de linhas X 29 bytes por linha = 145 bilhões de bytes = 135.0417 GB


ngram_key possui 73 bytes 64 bytes para ngram (ROW_FORMAT = FIXO define varchar como char) 8 bytes para ngram_id 1 sinalizador interno de exclusão do MyISAM de 1 byte

2 Entradas de índice para ngram_key = 64 bytes + 8 bytes = 72 bytes

47 milhões de linhas X 073 bytes por linha = 3431 milhões de bytes = 3,1954 GB
47 milhões de linhas X 072 bytes por linha = 3384 milhões de bytes = 3,1515 GB
47 milhões de linhas X 145 bytes por linha = 6815 milhões de bytes = 6,3469 GB

5 bilhões de linhas X 073 bytes por linha = 365 bilhões de bytes = 339.9327 GB
5 bilhões de linhas X 072 bytes por linha = 360 bilhões de bytes = 335.2761 GB
5 bilhões de linhas X 145 bytes por linha = 725 bilhões de bytes = 675.2088 GB

RolandoMySQLDBA
fonte
Obrigado pelas duas ótimas respostas. Estou curioso, qual é a razão para usar este método blackhole + trigger para preencher a tabela?
Dolan Antenucci 24/10/11
O buraco negro aceita o ngram orignal. O gatilho cria um mecanismo limpo INSERT IGNORE para dividir ngram do valor de auto_increment.
RolandoMySQLDBA
3

Aqui está uma sugestão bastante selvagem

Converta todos os ngrams em chaves MD5 de 32 caracteres

Esta tabela conterá todos os ngrams de qualquer tamanho (até 255 caracteres), 1 grama, 2 gramas, etc.

use test
DROP TABLE ngram_node;
DROP TABLE ngram_blackhole;
CREATE TABLE ngram_node
(
  NGRAM_KEY  CHAR(32) NOT NULL,
  NGRAM_YEAR SMALLINT NOT NULL,
  M_COUNT    SMALLINT NOT NULL,
  P_COUNT    SMALLINT NOT NULL,
  V_COUNT    SMALLINT NOT NULL,
  PRIMARY KEY   (NGRAM_KEY,NGRAM_YEAR)
) ENGINE=MyISAM
PARTITION BY KEY(NGRAM_KEY)
PARTITIONS 256;
CREATE TABLE ngram_blackhole
(
  NGRAM      VARCHAR(255) NOT NULL,
  NGRAM_YEAR SMALLINT NOT NULL,
  M_COUNT    SMALLINT NOT NULL,
  P_COUNT    SMALLINT NOT NULL,
  V_COUNT    SMALLINT NOT NULL
) ENGINE=BLACKHOLE;
DELIMITER $$
CREATE TRIGGER populate_ngram AFTER INSERT ON ngram_blackhole FOR EACH ROW
BEGIN
    INSERT INTO ngram_node VALUES (MD5(NEW.NGRAM),NEW.NGRAM_YEAR,NEW.M_COUNT,NEW.P_COUNT,NEW.V_COUNT);
END; $$
DELIMITER ;
INSERT INTO ngram_blackhole VALUES
('rolando',1965,31,29,85),
('pamela',1971,33,21,86),
('dominique',1996,30,18,87),
('diamond',1998,13,28,88),
('rolando edwards',1965,31,29,85),
('pamela edwards',1971,33,21,86),
('dominique edwards',1996,30,18,87),
('diamond edwards',1998,13,28,88),
('rolando angel edwards',1965,31,29,85),
('pamela claricia edwards',1971,33,21,86),
('dominique sharlisee edwards',1996,30,18,87),
('diamond ashley edwards',1998,13,28,88);
SELECT * FROM ngram_node;

A razão pela qual escolhi 256 partições decorre do fato de a função MD5 retornar 16 caracteres distintos (todos os dígitos hexadecimais). Os primeiros dois bytes são 16 x 16, 256.

Aqui estão os resultados do MySQL 5.5.11 no meu Windows 7 Desktop

mysql> use test
Database changed
mysql> DROP TABLE ngram_node;
Query OK, 0 rows affected (0.22 sec)

mysql> DROP TABLE ngram_blackhole;
Query OK, 0 rows affected (0.11 sec)

mysql> CREATE TABLE ngram_node
    -> (
    ->   NGRAM_KEY  CHAR(32) NOT NULL,
    ->   NGRAM_YEAR SMALLINT NOT NULL,
    ->   M_COUNT    SMALLINT NOT NULL,
    ->   P_COUNT    SMALLINT NOT NULL,
    ->   V_COUNT    SMALLINT NOT NULL,
    ->   PRIMARY KEY    (NGRAM_KEY,NGRAM_YEAR)
    -> ) ENGINE=MyISAM
    -> PARTITION BY KEY(NGRAM_KEY)
    -> PARTITIONS 256;
Query OK, 0 rows affected (0.36 sec)

mysql> CREATE TABLE ngram_blackhole
    -> (
    ->   NGRAM      VARCHAR(255) NOT NULL,
    ->   NGRAM_YEAR SMALLINT NOT NULL,
    ->   M_COUNT    SMALLINT NOT NULL,
    ->   P_COUNT    SMALLINT NOT NULL,
    ->   V_COUNT    SMALLINT NOT NULL
    -> ) ENGINE=BLACKHOLE;
Query OK, 0 rows affected (0.11 sec)

mysql> DELIMITER $$
mysql> CREATE TRIGGER populate_ngram AFTER INSERT ON ngram_blackhole FOR EACH ROW
    -> BEGIN
    ->  INSERT INTO ngram_node VALUES (MD5(NEW.NGRAM),NEW.NGRAM_YEAR,NEW.M_COUNT,NEW.P_COUNT,NEW.V_COUNT);
    -> END; $$
Query OK, 0 rows affected (0.05 sec)

mysql> DELIMITER ;
mysql> INSERT INTO ngram_blackhole VALUES
    -> ('rolando',1965,31,29,85),
    -> ('pamela',1971,33,21,86),
    -> ('dominique',1996,30,18,87),
    -> ('diamond',1998,13,28,88),
    -> ('rolando edwards',1965,31,29,85),
    -> ('pamela edwards',1971,33,21,86),
    -> ('dominique edwards',1996,30,18,87),
    -> ('diamond edwards',1998,13,28,88),
    -> ('rolando angel edwards',1965,31,29,85),
    -> ('pamela claricia edwards',1971,33,21,86),
    -> ('dominique sharlisee edwards',1996,30,18,87),
    -> ('diamond ashley edwards',1998,13,28,88);
Query OK, 12 rows affected (0.18 sec)
Records: 12  Duplicates: 0  Warnings: 0

mysql> SELECT * FROM ngram_node;
+----------------------------------+------------+---------+---------+---------+
| NGRAM_KEY                        | NGRAM_YEAR | M_COUNT | P_COUNT | V_COUNT |
+----------------------------------+------------+---------+---------+---------+
| 2ca237192aaac3b3a20ce0649351b395 |       1996 |      30 |      18 |      87 |
| 6f7fd3368170c562604f62fb4e92056d |       1965 |      31 |      29 |      85 |
| fb201333fef377917be714dabd3776d9 |       1971 |      33 |      21 |      86 |
| 4f79e21800ed6e30be4d1cb597f910c6 |       1971 |      33 |      21 |      86 |
| 9068e0de9f3fd674d4fa7cbc626e5888 |       1998 |      13 |      28 |      88 |
| 8a18abe90f2612827dc3a215fd1905d3 |       1965 |      31 |      29 |      85 |
| be60b431a46fcc7bf5ee4f7712993e3b |       1996 |      30 |      18 |      87 |
| c8adc38aa00759488b1d759aa8f91725 |       1996 |      30 |      18 |      87 |
| e80d4ab77eb18a4ca350157fd487d7e2 |       1965 |      31 |      29 |      85 |
| 669ffc150d1f875819183addfc842cab |       1971 |      33 |      21 |      86 |
| b685323e9de65080f733b53b2305da6e |       1998 |      13 |      28 |      88 |
| 75c6f03161d020201000414cd1501f9f |       1998 |      13 |      28 |      88 |
+----------------------------------+------------+---------+---------+---------+
12 rows in set (0.00 sec)

mysql>

Observe que eu carreguei 1, 2 e 3 gramas na mesma tabela, mas você não tem idéia de qual MD5 pertence a qual ngram. Assim, todos os ngrams podem se adaptar a essa tabela. Lembre-se de inserir na tabela ngram_blackhole e o resto é feito para você.

Você deve consultar a tabela ngram_node usando o MD5 () do ngram, independentemente de qual ngram.

mysql> select * from ngram_node where ngram_key=MD5('rolando edwards');
+----------------------------------+------------+---------+---------+---------+
| NGRAM_KEY                        | NGRAM_YEAR | M_COUNT | P_COUNT | V_COUNT |
+----------------------------------+------------+---------+---------+---------+
| 6f7fd3368170c562604f62fb4e92056d |       1965 |      31 |      29 |      85 |
+----------------------------------+------------+---------+---------+---------+
1 row in set (0.05 sec)

Se você deseja separar 1 grama, 2 gramas e 3 gramas em repositórios separados, basta criar outra tabela, outra tabela de buraco negro e outro gatilho na tabela de buraco negro para inserir na outra tabela.

Além disso, se seus ngrams forem maiores que 255 (se você estiver fazendo 7 ou 8 gramas), apenas aumente o tamanho VARCHAR da coluna NGRAM na tabela ngram_blackhole.

De uma chance !!!

ATUALIZAR

Na questão, foi declarado que 47 milhões de linhas foram carregadas no mysql. Para o meu layout de tabela sugerido, observe o seguinte:

ngram_node é 41 bytes por linha: 32 para NGRAM_KEY
8 para os números (2 para cada SMALLINT)
1 para o sinalizador interno MyISAM DELETED

Cada entrada de índice de chave primária teria 34 bytes
32 para NGRAM_KEY
2 para NGRAM_YEAR

47 milhões de linhas X 41 bytes por linha = 1,927 bilhão de bytes, cerca de 1,79466 GB.
47 milhões de linhas X 34 bytes por entrada de índice = 1,598 bilhão de bytes, cerca de 1,48825 GB.
O consumo da tabela MyISAM deve ser de um total combinado de 3,28291 GB.

A pergunta também mencionou o carregamento de 5 bilhões de linhas.

5 bilhões de linhas X 41 bytes por linha = 205 bilhões de bytes, cerca de 190,9211 GB.
5 bilhões de linhas X 34 bytes por entrada de índice = 170 bilhões de bytes, cerca de 158,3248 GB.
O consumo da tabela MyISAM deve ser de um total combinado de 349,2459 GB.

Observe que a taxa de crescimento do espaço usado na tabela MyISAM é linear devido à chave primária de tamanho constante. Agora você pode planejar o espaço em disco com base nisso.

RolandoMySQLDBA
fonte
11
Pensei na minha resposta e tenho em mente outra sugestão para que menos espaço em disco seja usado. Vou abordar isso na segunda-feira !!! Tenha um bom fim de semana.
RolandoMySQLDBA