Academia Portugal: Call for Papers

February 25th, 2010 ntavares Posted in pt_PT, wikipedia No Comments »

Está aberta a Chamada a Oradores para o primeiro evento organizado pela Associação Wikimedia Portugal (WMP)!

A Wikimedia Portugal, em conjunto com a Faculdade de Engenharia da Universidade do Porto (FEUP), está a organizar o primeiro Workshop em Portugal intitulado “Academia Wikipédia” no Porto/Exponor, a realizar em simultâneo com a “Qualifica”- Feira de educação, formação, juventude e emprego, no dia 16 de Abril.

Aqui está um pequeno descritivo da Academia:
Academia Wikipédia

As apresentações deverão ser em português, sempre que possível, e devem demorar entre 20min a 30min, com alguns minutos para perguntas e respostas. Se houver necessidade de estender este tempo penso que não será impossível.

Sou eu que estou a organizar o programa, e estou a criar uma maquete aqui: Programa da Academia Wikipédia. Enviem temas que gostavam de ver abordados, se os apresentados (a título de exemplo) não vos puxa para falar. Entrem em contacto comigo, ou usem a página de contactos da WMP.

AddThis Social Bookmark Button

Início das actividades da Wikimedia Portugal

January 25th, 2010 ntavares Posted in pt_PT, wikipedia No Comments »

Este post já deveria ter chegado há muito mais tempo. Mas o tempo não o permitiu…

Não deve ser novidade que a Wikimedia Portugal (WMP) já arrancou o Plano de Actividades para 2010-11. A primeira actividade oficial foi uma apresentação num seminário no Instituto Superior Técnico promovido pela Presidência do Departamento de Engenharia Informática, a convite do prof. José Borbinha, que gostámos muito de conhecer e a quem agradecemos o apoio e disponibilidade que demonstrou para connosco.

A Susana fez uma exposição da Wikimedia Foundation, do nosso contexto WMP, do processo editorial, da estrutura interna dos projectos (utilizadores, categorias, etc), da manutenção, licenciamento, etc.

A apresentação está aqui:
http://wikimedia.pt/download/Wikimedia_Slideshow.pps

Eu juntei-me à festa, atendendo a um público de informática, e apresentei brevemente a plataforma da WMF (servidores, software, arquitectura) mas o grosso da minha mini-apresentação foi para falar de predefinições, dados estruturados e seus benefícios na Wikipédia e, por fim, divaguei um bocadinho até à Web Semântica, conceito para o qual a Wikipédia está a ser bastante utilizada (os tópicos estão resumidos em 2 posts que já tinha feito no blog [1][2]).

A apresentação está aqui:
http://wikimedia.pt/download/Wikimedia_Web_Semantica.pps

AddThis Social Bookmark Button

Ainda sobre o tamanho dos campos VARCHAR…

January 25th, 2010 ntavares Posted in mysql, performance, pt_PT No Comments »

Em Setembro escrevi sobre as consequências de deixar ao acaso o tamanho e character set de campos VARCHAR. Entretanto gostaria de complementar as observações com o que escrevo abaixo.

A mistura de charsets — como ter uma tabela num charset e um determinado campo dessa tabela com outro charset diferente, por exemplo — deve ser cuidadosamente analisada. Existe alguma probabilidade de ocorrerem complicações, caso se trate esta questão levianamente.

À parte do que se deve ou não fazer, o que eu gostaria era que se compreendesse o benefício que se pode atingir no caso particular da utilização de UUIDs. Já vimos que UTF-8 para este caso particular é um desperdício, mas o que gostaria de demonstrar são se, de facto, compensa alterar campos específicos quando nem todos podem ser alterados.

Vejamos: vão ser criadas 2 tabelas MyISAM em UTF-8, uma com um campo Latin1 e outra com o campo natural, em UTF-8. Vou usar uma tabela que tinha para aqui com 2M de UUIDs apenas para acelerar o processo. Essa tabela, table_uuid, é original Latin1, embora o foco não seja sobre o carregamento dos dados, mas sim do seu cruzamento entre as duas primeiras:

MySQL:
  1. mysql> CREATE TABLE charset_latin1 ( id CHAR(36) CHARSET latin1, PRIMARY KEY (id) ) CHARSET utf8;
  2. Query OK, 0 rows affected (0.01 sec)
  3.  
  4. mysql> CREATE TABLE charset_utf8 ( id CHAR(36) CHARSET utf8, PRIMARY KEY (id) ) CHARSET utf8;
  5. Query OK, 0 rows affected (0.01 sec)
  6.  
  7. mysql> INSERT INTO charset_utf8 SELECT id FROM table_uuid ;
  8. Query OK, 2097152 rows affected (2 min 29.77 sec)
  9. Records: 2097152  Duplicates: 0  WARNINGS: 0
  10.  
  11. mysql> INSERT INTO charset_latin1 SELECT id FROM table_uuid ;
  12. Query OK, 2097152 rows affected (50.15 sec)
  13. Records: 2097152  Duplicates: 0  WARNINGS: 0

Agora, suponhamos que a tabela charset_latin1 é uma tabela originalmente em UTF-8, para a qual já fizemos a transformação do campo (que vai albergar UUIDs) para Latin1. O que pretendemos demonstrar são os ganhos/prejuízos do cruzamento de tabelas com as mesmas características, e com diferentes:

MySQL:
  1. mysql> SELECT count(1) FROM charset_utf8 a, charset_latin1 b WHERE a.id = b.id;
  2. +----------+
  3. | count(1) |
  4. +----------+
  5. 2097152 |
  6. +----------+
  7. 1 row in SET (1 min 8.82 sec)
  8.  
  9. mysql> SELECT count(1) FROM charset_utf8 a, charset_utf8 b  WHERE a.id = b.id;
  10. +----------+
  11. | count(1) |
  12. +----------+
  13. 2097152 |
  14. +----------+
  15. 1 row in SET (58.00 sec)
  16.  
  17. mysql> SELECT count(1) FROM charset_latin1 a, charset_latin1 b  WHERE a.id = b.id;
  18. +----------+
  19. | count(1) |
  20. +----------+
  21. 2097152 |
  22. +----------+
  23. 1 row in SET (24.43 sec)

Ou seja, como já se disse, fazer JOINs com Latin1 é muito rápido, relativamente às outras opções. Mas repare-se quando se cruzam diferentes charsets (1º JOIN): ocorre uma degradação de 19% face ao pior caso.

Ou seja, tendo em conta que estas tabelas podem ser apenas tabelas de relação [quero com isto dizer que não contêm dados que não sirvam senão para cruzamentos/JOINs] é expectável que a performance se degrade sempre que sejam cruzadas com outras cuja chave não esteja no mesmo charset. Deduz-se daqui que existe, portanto, overhead relativo às conversões de charset durante o JOIN.

Apesar de estarmos perante um index scan (ie, com os dados todos em cache), ocorreu-me também experimentar com InnoDB.

MySQL:
  1. mysql> ALTER TABLE charset_utf8 engine=INNODB;
  2. Query OK, 2097152 rows affected (48.18 sec)
  3. Records: 2097152  Duplicates: 0  WARNINGS: 0
  4.  
  5. mysql> ALTER TABLE charset_latin1 engine=INNODB;
  6. Query OK, 2097152 rows affected (39.43 sec)
  7. Records: 2097152  Duplicates: 0  WARNINGS: 0

Convém ressalvar aqui que fiz uns SELECTs primeiro para ter a certeza que os datafiles estavam carregados na buffer pool.

MySQL:
  1. mysql> SELECT count(1) FROM charset_utf8 a, charset_latin1 b WHERE a.id = b.id;
  2. +----------+
  3. | count(1) |
  4. +----------+
  5. 2097152 |
  6. +----------+
  7. 1 row in SET (21.65 sec)
  8.  
  9. mysql> SELECT count(1) FROM charset_utf8 a, charset_utf8 b  WHERE a.id = b.id;
  10. +----------+
  11. | count(1) |
  12. +----------+
  13. 2097152 |
  14. +----------+
  15. 1 row in SET (12.61 sec)
  16.  
  17. mysql> SELECT count(1) FROM charset_latin1 a, charset_latin1 b  WHERE a.id = b.id;
  18. +----------+
  19. | count(1) |
  20. +----------+
  21. 2097152 |
  22. +----------+
  23. 1 row in SET (8.25 sec)

Ou seja, a conclusão que se pode tirar daqui é que: não só devemos pensar na optimização que se ganha com a escolha do charset, mas também devemos analisar o impacto de não podermos levar a tarefa de reconversão avante na totalidade. A necessidade de cruzar campos com charsets diferentes torna-se, efectivamente, mais penoso. Resta comparar, caso a caso, o benefício que se obtém em queries pouco/nada optimizadas versus o prejuízo de performance que poderá reflectir-se em cruzamentos/JOINs com campos em charsets diferentes.

AddThis Social Bookmark Button

As prendas da MySQL para 2009

December 30th, 2009 ntavares Posted in clustering, mysql, pt_PT No Comments »

Bem, já lá vai algum tempito, mas aproveitei o tempo de férias para dar algum feedback do que de novo apareceu relativamente ao MySQL.

Começo por sinalizar o aparente descontinuamento do ramo 5.0.x do MySQL, pelo menos no que toca à manutenção activa. Também já não era sem tempo, já que a versão 5.1 trouxe demasiadas coisas boas para ser ignorada.

A seguir deve seguir-se a 5.4.x; as novidades foram muito bem recebidas, sobretudo pela malta do Solaris e do InnoDB que, por sua vez - e devido ao facto do InnoDB passar a ser desenvolvido em forma de plugin - deverá evoluir nestas questões de forma independente do core. Mas a grande grande novidade...

... é o surgimento da versão 5.5 como milestone 2 a caminho do próximo ramo GA que, além de fundir as novidades da 5.4, oferece-nos (finalmente!):

  • a possibilidade de alargar o particionamento para além das funções de particionamento, que eram pouquíssimas e até para particionar por data eram necessários alguns estratagemas;
  • o suporte para key caches por partição (fantástico!);
  • as tão esperadas funções SIGNAL e RESIGNAL - as marteladas que eram necessárias para contornar esta lacuna eram terríveis de manter :);
  • o suporte para a replicação semi-síncrona sob a forma de plugin;
  • e outras, que não obstante não serem enumeradas por mim, não devem ser descuradas na leitura!!

Claro que já houve experimentadores destas tecnologias, e aqui vos deixo mais material de leitura:

Convido-vos a deixarem aqui links para os vossos artigos e comentários sobre as restantes funcionalidades.

AddThis Social Bookmark Button

Actualização das páginas órfãs

October 18th, 2009 ntavares Posted in pt_PT, wikipedia No Comments »

A pedido do Lijealso, aqui vai uma actualização das estatísticas incompletas da Wikipédia lusófona para o caso das páginas órfãs.

Constatou-se que o dump utilizado anteriormente era insuficiente, pelo que se descarregou a tabela pagelinks, desta vez do dump de 20091015. Para se excluir os redireccionamentos, importou-se também a tabela redirect.

Fartei-me entretanto de alternar entre o que estava a fazer e a lista de códigos de domínios, pelo que criei uma pequena tabela auxiliar:

MySQL:
  1. mysql> CREATE TABLE _namespaces ( id TINYINT NOT NULL, namespace VARCHAR(50), PRIMARY KEY (id) );
  2. Query OK, 0 rows affected (0.01 sec)
  3.  
  4. mysql> INSERT INTO _namespaces VALUES (-2,'Media'),(-1,'Especial'),(0,''),(1,'Discussão'),(2,'Usuário'),(3,'Usuário Discussão'),(4,'Wikipedia'),(5,'Wikipedia Discussão'),(6,'Ficheiro'),(7,'Ficheiro Discussão'),(8,'MediaWiki'),(9,'MediaWiki Discussão'),(10,'Predefinição'),(11,'Predefinição Discussão'),(12,'Ajuda'),(13,'Ajuda Discussão'),(14,'Categoria'),(15,'Categoria Discussão'),(100,'Portal'),(101,'Portal Discussão'),(102,'Anexo'),(103,'Anexo Discussão');
  5. Query OK, 22 rows affected (0.00 sec)
  6. Records: 22  Duplicates: 0  WARNINGS: 0

O resultado deu-me um incrível total de 769854 páginas órfãs, pelo que decidi separá-las por namespace para permitir prioritizar a análise:

MySQL:
  1. mysql> SELECT p.page_namespace,count(1) FROM page p
  2.     -> LEFT JOIN redirect  r
  3.     -> ON p.page_id = r.rd_from
  4.     ->
  5.     -> LEFT JOIN pagelinks pl
  6.     -> on pl.pl_namespace = p.page_namespace
  7.     -> and pl.pl_title = p.page_title
  8.     ->
  9.     -> WHERE r.rd_from IS NULL
  10.     -> AND pl.pl_from IS NULL
  11.     ->
  12.     -> GROUP BY p.page_namespace;
  13. +----------------+----------+
  14. | page_namespace | count(1) |
  15. +----------------+----------+
  16. |              0 |    12958 |
  17. |              1 |   103645 |
  18. |              2 |    16592 |
  19. |              3 |   568675 |
  20. |              4 |     1954 |
  21. |              5 |      856 |
  22. |              8 |      773 |
  23. |              9 |       17 |
  24. |             10 |     7522 |
  25. |             11 |     1014 |
  26. |             12 |        3 |
  27. |             13 |       27 |
  28. |             14 |    51735 |
  29. |             15 |     1315 |
  30. |            100 |     1190 |
  31. |            101 |      117 |
  32. |            102 |      173 |
  33. |            103 |     1288 |
  34. +----------------+----------+
  35. 18 rows in SET (20.90 sec)

O resultado do cruzamento das duas tabelas foi afixado aqui, com uma listagem de 15M para os 12958 artigos no domínio principal. Na verdade, esta listagem foi feita para colar numa página wiki, no entanto tenham em atenção que são 15M, pelo que não recomendo fazê-lo. Têm outras listas (como a mais simples, em formato pageid,namespace,title) nessa directoria.

AddThis Social Bookmark Button

Revisita aos dumps da Wikipédia

October 6th, 2009 ntavares Posted in pt_PT, wikipedia 1 Comment »

Desta vez em português, decidi dar [alguma] continuidade ao que comecei há uns dias com a importação dos dumps da Wikipédia. Graças à dica do Rei-artur foi fácil extrair a lista de robôs, para excluir das estatísticas.

CODE:
  1. [myself@speedy ~]# wget 'http://pt.wikipedia.org/w/api.php?action=query&list=allusers&aufrom=A&augroup=bot&aulimit=500&format=txt' -q -O - > bots.tmp
  2.  
  3. [myself@speedy ~]# cat bots.tmp | grep '\[name\]' | sed 's,^.*\[name\] => ,,' > /tmp/bots.txt

Aproveitei e repesquei os user_id para simplificar as pesquisas sem fazer alterações na tabela user.

MySQL:
  1. mysql> CREATE TABLE user_bots ( bot_name VARCHAR(25) );
  2. Query OK, 0 rows affected (0.01 sec)
  3.  
  4. mysql> LOAD DATA INFILE '/tmp/bots.txt' INTO table user_bots;
  5. Query OK, 136 rows affected (0.00 sec)
  6. Records: 136  Deleted: 0  Skipped: 0  WARNINGS: 0
  7.  
  8.  
  9. mysql> ALTER TABLE user_bots add COLUMN bot_user_id INT;
  10. Query OK, 136 rows affected (0.01 sec)
  11. Records: 136  Duplicates: 0  WARNINGS: 0
  12.  
  13. mysql> ALTER TABLE user add index idx_t ( user_name );
  14. Query OK, 119134 rows affected (2.63 sec)
  15. Records: 119134  Duplicates: 0  WARNINGS: 0
  16.  
  17. mysql> UPDATE user_bots ub JOIN user u on user_name = bot_name SET ub.bot_user_id = u.user_id;
  18. Query OK, 134 rows affected (0.00 sec)
  19. Rows matched: 134  Changed: 134  WARNINGS: 0
  20.  
  21. mysql> ALTER TABLE user_bots add PRIMARY KEY (bot_user_id);
  22. Query OK, 136 rows affected, 1 warning (0.00 sec)
  23. Records: 136  Duplicates: 0  WARNINGS: 1
  24.  
  25. mysql> SHOW WARNINGS;
  26. +---------+------+---------------------------------------------------+
  27. | Level   | Code | Message                                           |
  28. +---------+------+---------------------------------------------------+
  29. | Warning | 1265 | Data truncated for COLUMN 'bot_user_id' at row 71 |
  30. +---------+------+---------------------------------------------------+
  31. 1 row in SET (0.00 sec)
  32.  
  33. mysql> UPDATE user_bots SET bot_user_id = -1 WHERE bot_user_id = 0;
  34. Query OK, 1 row affected (0.00 sec)
  35. Rows matched: 1  Changed: 1  WARNINGS: 0

Não tinha reparado que havia um utilizador/robô com o nome "MediaWiki default" mas, bem, depois de criar a Primary Key ficou com o bot_user_id=0 e, para evitar que coincidisse com o agregado para anonymous, dei-lhe o bot_user_id=-1.

Então agora já estamos prontos a completar a query onde ficámos no último dia (número de edições em artigos distintos em cada namespace por utilizador):

MySQL:
  1. mysql> EXPLAIN SELECT epn.user_name,epn.page_namespace,epn.edits
  2.     -> FROM edits_per_namespace epn
  3.     -> LEFT JOIN user_bots ub ON epn.user_id = ub.bot_user_id
  4.     -> WHERE ub.bot_user_id IS NULL
  5.     -> AND epn.user_id <> 0
  6.     -> ORDER BY edits desc limit 20;
  7. +----+-------------+-------+--------+---------------+---------+---------+----------------------+--------+--------------------------------------+
  8. | id | select_type | table | type   | possible_keys | key     | key_len | ref                  | rows   | Extra                                |
  9. +----+-------------+-------+--------+---------------+---------+---------+----------------------+--------+--------------------------------------+
  10. 1 | SIMPLE      | epn   | ALL    | NULL          | NULL    | NULL    | NULL                 | 187624 | USING WHERE; USING filesort          |
  11. 1 | SIMPLE      | ub    | eq_ref | PRIMARY       | PRIMARY | 4       | ntavares.epn.user_id |      1 | USING WHERE; USING index; NOT EXISTS |
  12. +----+-------------+-------+--------+---------------+---------+---------+----------------------+--------+--------------------------------------+
  13. 2 rows in SET (0.00 sec)
  14.  
  15. mysql> SELECT epn.user_name,epn.page_namespace,epn.edits
  16.     -> FROM edits_per_namespace epn
  17.     -> LEFT JOIN user_bots ub ON epn.user_id = ub.bot_user_id
  18.     -> WHERE ub.bot_user_id IS NULL
  19.     -> AND epn.user_id <> 0
  20.     -> ORDER BY edits desc limit 10;
  21. +----------------+----------------+-------+
  22. | user_name      | page_namespace | edits |
  23. +----------------+----------------+-------+
  24. | EMP,Nice poa   |              0 | 58138 |
  25. | Dantadd        |              0 | 44767 |
  26. | João Carvalho  |              3 | 44533 |
  27. | OS2Warp        |              0 | 43396 |
  28. | Yanguas,Sonlui |              0 | 37020 |
  29. | Lijealso       |              0 | 34157 |
  30. | Rei-artur      |              0 | 33863 |
  31. | Tumnus         |              3 | 33213 |
  32. | Nuno Tavares   |              0 | 31910 |
  33. | Bisbis         |              0 | 29886 |
  34. +----------------+----------------+-------+
  35. 10 rows in SET (0.76 sec)

Os resultados completos estão aqui.

Já agora, para finalizar, a tão afamada lista de wikipedistas por número de edições:

MySQL:
  1. mysql> CREATE TABLE edits_per_user SELECT rev_user,count(1) as counter FROM revision GROUP BY rev_user;
  2. Query OK, 119134 rows affected (12.61 sec)
  3. Records: 119134  Duplicates: 0  WARNINGS: 0
  4.  
  5. mysql> SELECT u.user_name,epu.counter
  6.     -> FROM edits_per_user epu
  7.     -> LEFT JOIN user_bots ub on ub.bot_user_id = epu.rev_user
  8.     -> JOIN user u on epu.rev_user = u.user_id
  9.     -> WHERE ub.bot_user_id IS NULL ORDER BY counter desc limit 10;
  10. +----------------+---------+
  11. | user_name      | counter |
  12. +----------------+---------+
  13. | anonymous      | 3119758 |
  14. | EMP,Nice poa   |  176338 |
  15. | OS2Warp        |  163751 |
  16. | Dantadd        |  105657 |
  17. | Lijealso       |   90025 |
  18. | Yanguas,Sonlui |   89152 |
  19. | Rei-artur      |   83662 |
  20. | Mschlindwein   |   75680 |
  21. | Bisbis         |   75361 |
  22. | Nuno Tavares   |   73141 |
  23. +----------------+---------+
  24. 10 rows in SET (0.05 sec)

Os resultados completos estão aqui.

AddThis Social Bookmark Button

Revisita aos dados estruturados

October 3rd, 2009 ntavares Posted in pt_PT, wikipedia No Comments »

Há alguns dias num mergulho profundo sobre a utilização de wikis em campos específicos deparei-me com uma "foto" da Wikipédia muito interessante, aqui, que ilustra, entre outras coisas, a actividade na Wikipédia, a vários níveis: Visualizing Science & Tech Activity in Wikipedia:


Fonte: A Beatiful WWW

O website, A Beatiful WWW, dedica-se à extracção e representação dos volumes de informação distintos que conhecemos hoje. Eu já tinha falado nisto e descobri, entretanto, que o Google disponibiliza uma API de representação de dados estruturados.

Consigo pensar numa série de brincadeiras para isto :) Imaginem, por exemplo, juntar isto tudo, logo agora que a Wikimedia vai estar empenhada em manter os conteúdos disponíveis no Amazon Public Data Sets!..

Olhem aqui um exemplo do que pode ser feito, desta vez com Hadoop e Hive: Hot Wikipedia Topics, Served Fresh Daily.

AddThis Social Bookmark Button

Tamanho e character set de campos VARCHAR e consequências

September 24th, 2009 ntavares Posted in mysql, performance, pt_PT 3 Comments »

Apesar do tamanho dos campos VARCHAR não influenciar o tamanho dos índices, este tipo de dados (e outros semelhantes) comportam uma série de características que podem afectar seriamente a performance. Vamos testar o impacto destes campos em dois cenários: com character sets diferentes e com tamanhos diferentes.

Tendo em conta que o storage engine MEMORY apenas trabalha com tamanhos de tuplos fixos (fixed-length rows), e que este engine é o utilizado para tabelas temporárias (uma coisa a evitar, embora nem sempre seja possível), as consequências podem ser desastrosas.

Para esta demonstração, vamos definir o tamanho máximo das tabelas MEMORY para um valor que possamos atingir em alguns segundos, o mínimo:

MySQL:
  1. mysql> SET max_heap_table_size = 1;
  2. Query OK, 0 rows affected, 1 warning (0.00 sec)
  3.  
  4. mysql> SHOW variables like '%heap%';
  5. +---------------------+-------+
  6. | Variable_name       | Value |
  7. +---------------------+-------+
  8. | max_heap_table_size | 16384 |
  9. +---------------------+-------+
  10. 1 row in SET (0.00 sec)

Observamos que o mínimo que conseguimos será 16KB. Vamos ver o que acontece com campos VARCHAR (ie, de comprimento [VAR]iável):

MySQL:
  1. mysql> CREATE TABLE `varchar_small` (
  2.     ->   `id` VARCHAR(36) NOT NULL
  3.     -> ) ENGINE=MEMORY DEFAULT CHARSET=utf8;
  4. Query OK, 0 rows affected (0.01 sec)
  5.  
  6. mysql> INSERT INTO varchar_small VALUES('abcd');
  7. Query OK, 1 row affected (0.00 sec)
  8.  
  9. mysql> -- Vamos enchendo ate nao dar mais....
  10. mysql> INSERT INTO varchar_small SELECT * FROM varchar_small;
  11. ERROR 1114 (HY000): The table 'varchar_small' is full
  12.  
  13. mysql> CREATE TABLE `var_char` (
  14.     ->   `id` VARCHAR(36) NOT NULL
  15.     -> ) ENGINE=MEMORY DEFAULT CHARSET=utf8;
  16. Query OK, 0 rows affected (0.00 sec)
  17.  
  18. mysql> INSERT INTO var_char VALUES('abcdefgh-ijkl-mnop-qrst-uvwxyzabcedf');
  19. Query OK, 1 row affected (0.00 sec)
  20.  
  21. mysql> -- O mesmo: vamos enchendo ate nao dar mais...
  22. mysql> INSERT INTO var_char SELECT * FROM var_char;
  23. ERROR 1114 (HY000): The table 'var_char' is full
  24.  
  25. mysql> SELECT count(1) FROM var_char;
  26. +----------+
  27. | count(1) |
  28. +----------+
  29. |      320 |
  30. +----------+
  31. 1 row in SET (0.00 sec)
  32.  
  33. mysql> SELECT count(1) FROM varchar_small;
  34. +----------+
  35. | count(1) |
  36. +----------+
  37. |      320 |
  38. +----------+
  39. 1 row in SET (0.00 sec)

Ora bem, o que fiz foi preencher a primeira tabela com conteúdo bem menor que 36 carácteres (apenas 4) e, na segunda, conteúdo que preenchesse o campo todo. O que podemos observar é que, neste storage engine, o espaço ocupado por um campo pouco cheio ou muito cheio é sempre o mesmo: é o tamanho total associado ao campo. É isso que significa fixed-length, e isto acarreta como consequência que o tamanho de um campo VARCHAR em tabelas do tipo MEMORY (leia-se também: tabelas temporárias) é invariavelmente o tamanho máximo do campo. Consequentemente, ainda que um tamanho máximo de 255 não influencie o tamanho de índice sobre esse campo, sempre que seja necessário transportar esses dados para tabelas temporárias pode verificar-se um desperdício enorme de espaço (multiplicar esse desperdício pelo número de linhas que terão que ser transportadas para a tabela temporária!).

Mas isto não fica por aqui: para além de cuidado e bom senso na definição do tamanho do campo, é necessário também ter em atenção o seu encoding. O segundo ponto é sobre a influência do charset. Tipicamente trabalhávamos em Latin1, mas com a disseminação da localização (i18n) passou-se praticamente a usar sempre UTF-8. Tudo muito bem, deixámos de ter uma série de problemas com a formatação das sequências de carácteres (vulgo strings). Mas nem tudo são rosas: o charset UTf-8 pode consumir até 4 carácteres (em MySQL, 3), ao passo que Latin1 usava apenas 1:

MySQL:
  1. mysql> CREATE TABLE `varchar_utf8` (
  2.     ->   `id` VARCHAR(36) NOT NULL
  3.     -> ) ENGINE=MEMORY DEFAULT CHARSET=utf8;
  4. Query OK, 0 rows affected (0.01 sec)
  5.  
  6. mysql> CREATE TABLE `varchar_latin1` (
  7.     ->   `id` VARCHAR(36) NOT NULL
  8.     -> ) ENGINE=MEMORY DEFAULT CHARSET=latin1;
  9. Query OK, 0 rows affected (0.00 sec)

A cláusula geral de encoding da tabela propaga-se como default para as colunas. De qualquer forma, poderia especificar o encoding para a coluna de forma individual. Para ter a certeza que não estamos a ser enganados pelo encoding corrente no cliente mysql, usei dois scripts em PHP para criar uma única linha para cada uma destas tabelas (o encoding no meu terminal é UTF-8, como já vai sendo comum):

PHP:
  1. mysql_connect('localhost','root','');
  2. mysql_query("SET NAMES UTF8");
  3. mysql_query("INSERT INTO varchar_utf8 VALUES('ãããããããã-ãããã-ãããã-ãããã-ãããããããããããã')");

PHP:
  1. mysql_connect('localhost','root','');
  2. mysql_query("SET NAMES Latin1");
  3. mysql_query("INSERT INTO varchar_latin1 VALUES('ãããããããã-ãããã-ãããã-ãããã-ãããããããããããã')");
  4. EOF

Gravei os dois scripts acima para varchar_utf8.php e varchar_latin1.php.temp, respectivamente. Por fim, alterei o encoding do script para em Latin1 (porque faça o que fizer, o encoding do meu terminal é UTF-8):

CODE:
  1. [root@speedy ~]# cat varchar_latin1.php.temp | iconv -f utf8 -t iso-8859-1 > varchar_latin1.php

E executei os dois:

CODE:
  1. [root@speedy ~]# php -f varchar_utf8.php
  2. [root@speedy ~]# php -f varchar_latin1.php

OK, agora tenho 1 registo de cada em cada tabela. Vamos usar esse único registo em cada para encher as tabelas até não ser possível adicionar mais registos:

MySQL:
  1. mysql> -- ...
  2. mysql> INSERT INTO varchar_utf8 SELECT * FROM varchar_utf8;
  3. ERROR 1114 (HY000): The table 'mem_utf8' is full
  4.  
  5. mysql> -- ...
  6. INSERT INTO varchar_latin1 SELECT * FROM varchar_latin1;
  7. ERROR 1114 (HY000): The table 'mem_latin1' is full
  8.  
  9. mysql> SELECT count(1) FROM mem_utf8;
  10. +----------+
  11. | count(1) |
  12. +----------+
  13. |      126 |
  14. +----------+
  15. 1 row in SET (0.00 sec)
  16.  
  17. mysql> SELECT count(1) FROM mem_latin1;
  18. +----------+
  19. | count(1) |
  20. +----------+
  21. |      320 |
  22. +----------+
  23. 1 row in SET (0.00 sec)

Pois é, verificámos o segundo problema: no engine MEMORY couberam muito menos linhas na primeira tabela (UTF-8) que na primeira (Latin1) porque que os campos são do seu tamanho máximo possível e, no caso dos VARCHAR em UTF8, cada carácter pode ocupar até 3 bytes. Ora, para caberem 36 carácteres, serão necessários, pelo menos, 36*3 = 108 bytes! Isto representa um consumo de 300% que pode não ter sido estimado no que respeita ao tamanho de memória permitido para tabelas temporárias.

Ambos cenários são bastante frequentes, especialmente em aplicações que usem frameworks - e também nestes casos, pela generalidade de backends que tentam suportar (MySQL, PostgreSQL, Oracle, etc), as queries envolvidas não costumam ser optimizadas para nenhum motor em particular; no caso do MySQL essas queries geram, muitas vezes, tabelas temporárias sem necessidade e optimizar este tipo de queries pode implicar alterações no núcleo da framework ou, no mínimo, criar excepções à regra.

A escolha do nome da coluna nos exemplos acima não foi ao acaso: com efeito, os campos UUID() costumam ser UTF-8 apenas porque toda a tabela é UTF-8 e, imagine-se, não há nenhum carácter num UUID() que não seja ASCII (na verdade, não há nenhum carácter que não seja [a-zA-Z-1-9]!):

MySQL:
  1. mysql> SELECT UUID();
  2. +--------------------------------------+
  3. | UUID()                               |
  4. +--------------------------------------+
  5. | 27d6a670-a64b-11de-866d-0017083bf00f |
  6. +--------------------------------------+
  7. 1 row in SET (0.00 sec)

Como o conjunto de caracteres de um UUID() é charset compatible entre os vários, esta é uma das optimizações que poderão fazer nas vossas aplicações facilmente, contribuindo para o aumento de performance do MySQL sem qualquer impacto na vossa aplicação.

AddThis Social Bookmark Button

Novidades na integração SugarCRM - IPBrick

September 15th, 2009 ntavares Posted in pt_PT, sugarcrm No Comments »

Para a versão GA do SugarCRM 5.5, estamos a preparar algumas surpresas para a versão 5.1 da IPBrick:

  • Disponibilização do suporte integrado para qualquer das versões SugarCRM 5.2 e 5.5 (Community Edition, Professional ou Enterprise).
  • Novo método de sincronização/importação de contas e contactos. Este método reduz exponencialmente a velocidade de sincronização: quanto maiores forem os dados a importar, maior será a diferença na rapidez.
  • Graças a este novo método vai ser possível também algum nível de sincronização bidireccional. Na verdade trata-se de uma fusão - tanto quanto possível - de dois registos em alterados em ambos lados. O utilizador poderá controlar como pretende os resultados:
    • sincronização estrita com o IP Contactos
    • sincronização dos comuns (entre IPBrick e SugarCRM) mas preservando os dados isolados do SugarCRM (ie, que não existam no IPBrick), permitindo ao SugarCRM desenvolver autonomia
    • ou sincronização de apenas os dados novos do IP Contactos, preservando por completo os registos do SugarCRM.
  • Melhor integração com o SugarCRM: a nova versão está muito mais robusta no que toca a alterações upgrade safe e a DRI refez a integração para isso mesmo, o que significa que novas versões serão lançadas mais rapidamente.
  • A existência de uma camada de abstracção, possibilitando testar directa e instantaneamente sobre dados reais dos clientes. Ainda será desenvolvida uma funcionalidade de ofuscamento de dados para permitir manter a confidencialidade desses dados.
  • A possibilidade de realizar sobre o capítulo de sincronização uma bateria de testes de validação (unit testing) automatizados. Esta medida vai-nos possibilitar fazer controlo de qualidade antes de cada versão do módulo.
  • E claro, não menos importante, um footprint de memória muito reduzido (< 1MB na linha de comandos);
  • A nova interface de administração, com um assistente que vai explicando os passos a seguir, oferece agora a possibilidade de extrair relatórios da sincronização (ver abaixo) e permite a revisão do resultado final antes de ser fundido com o SugarCRM:

Quadro inicial:
sugar-ipbrick-uirevamp1

O primeiro passo é a importação dos dados do IP Contacts e cruzamento com os dados actuais do SugarCRM. No final do processo será possível rever as operações:
sugar-ipbrick-uirevamp2

Finalmente, o último passo é a fusão:
sugar-ipbrick-uirevamp3

De resto, estamos ainda a afinar os últimos detalhes do módulo de integração com as comunicações unificadas, que será também adaptado para as alterações da IPBrick 5.2.

AddThis Social Bookmark Button

SugarCRM data generator

August 31st, 2009 ntavares Posted in performance, pt_PT, sugarcrm No Comments »

O SugarCRM dispõe de um gerador de dados para popular a DB com accounts, contacts, etc de exemplo. No entanto, pode ser utilizado para gerar DB's para testes, nomeadamente para testes de carga. Segundo o que percebi, este data generator usa um seed fixo para que os dados gerados para uma DB sejam os mesmos para outra, para que se possam comparar entre si, por exemplo. Eis os passos necessários [usados na versão 5.5]:

Procurar large_scale_test no config.php (próx. à linha 200) e alterar para true:

CODE:
  1. 'large_scale_test' => false,
  2. 'large_scale_test' => true,

Colocar o script seguinte em install/dataGeneratorKit.php. Este script servirá de wrapper para o populateSeedData.php que já dispõe da lógica para popular a DB em termos propocionais:

PHP:
  1. define('sugarEntry',1);
  2. require_once('include/entryPoint.php');
  3. require_once('install/install_utils.php');
  4.  
  5. require_once('modules/TableDictionary.php');
  6.  
  7. require_once "include/database/DBManagerFactory.php";
  8. include "install/populateSeedData.php";
  9. ?>

Talvez seja necessário comentar algumas linhas dos ficheiros i18n, parecidas às que se mostram abaixo (fazem referência a SugarThemeRegistry). Pelo menos em 5.5beta foi:

PHP:
  1. ...
  2. //    'LBL_EMAIL_ADDRESS_BOOK_TITLE_ICON'     => 'getImageURL('icon_email_addressbook.gif').' align=absmiddle border=0> Address Book',
  3. //    'LBL_EMAIL_ADDRESS_BOOK_TITLE_ICON_SHORT'     => 'getImageURL('icon_email_addressbook.gif').' align=absmiddle border=0> Addr...',
  4. ...

Se necessário, alterar os valores no ficheiro install/populateSeedData.php:

PHP:
  1. if($large_scale_test) {
  2.         // increase the cuttoff time to 1 hour
  3.         ini_set("max_execution_time", "3600");
  4.         $number_contacts = 100000;
  5.         $number_companies = 15000;
  6.         $number_leads = 100000;
  7. }

Finalmente, executar:

CODE:
  1. php -f install/dataGeneratorKit.php

Notas

  • Não sei até que ponto o script é eficiente, pois já me crashou com ''memory exhausted'' nos 256MB de memory_limit.

Aproveitei e copiei este artigo para a Wiki da SugarCRM.

AddThis Social Bookmark Button