Patrocinadores

Você está aquiLeitor de feeds

Leitor de feeds


IBM Knowledge Center

Informix-Techonology - seg, 14/04/2014 - 15:00

This article is written in Englsih and Portuguese (original version here)
Este artigo está escrito em Inglês e Português (versão original aqui)


English version
IBM already announced this, at least on this post from the IBM Technical Content blog, but I think it's appropriate to mention it here. The traditional Infocenters, which are the standard IBM documentation online sites, will be discontinued in a near future.
IBM  has replaced all the InfoCenters (one for each product/version) by a centralized site that contains all the documentation for all product/versions. This new site is the IBM Knowledge Center.

What does it mean for us, the users? Not much... it means we'll have to update our links, or better saying, changing several links into just one where we can easily browse the product/versions.
But the new site tries to improve on something that was already introduced into the Infocenters: It will adapt to you and to your needs provided you "login" to the site with your ibm.com user credentials.
IBM Knowledge Center will allow you to:

  • search more effectively
  • create your own bookmarks
  • share the content through email or social networks
  • export content in PDF format
  • add comments
  • provide feedback to IBM
Naturally it's expected this will also bring benefits to IBM. These may include:
  • Having all Infocenters in a single interface brings simplicity and easier management
  • The benefits of introducing a functionality will be available to all products/versions
  • Better customer interaction
 But these will probably reflect into an improvement of customer experience while using the online documentation.
The date for retirement of the Infocenters is yet to be announced, but I urge you to addopt Knownledge Center. You'll notice it works faster than Infocenters and after the initial adaptation period (to get used to the slightly different interface) you'll notice it's better than the previous interfaces. For detailed information, please check the Chat with the Labs webcast about this topic, available at:

https://www.ibm.com/developerworks/community/wikis/home?lang=en_US#!/wiki/Informix+Chat+With+the+Lab/page/Welcome


Versão Portuguesa
A IBM já anunciou isto, pelo menos neste artigo do blog IBM Technical Content, mas penso que é apropriado referi-lo também aqui. Os tradicionais Infocenters, que são os sites de documentação online serão descontinuados num futuro próximo.
A IBM substitui-o todos os Infocenters (um para cada versão de cada produto) por um sitio centralizado que contém toda a documentação de todos os produtos e versões. O novo site é o IBM Knowledge Center.

O que é que isto significa para todos nós, utilizadores? Não muito.... Significa que teremos de atualizar os nossos links, ou dizendo melhor, mudar vários links para um único onde podemos facilmente percorrer as várias versões dos produtos. Mas o novo site tenta melhorar algo que já tinha sido introduzido nos Infocenters: Tenta adaptar-se a si e às suas necessidades, desde que se autentique com as suas credenciais de ibm.com.
O IBM Knowledge Center permite-lhe:
  • Efetuar pesquisas de forma mais eficaz
  • Criar os seus próprios marcadores ou apontadores para zonas da documentação
  • Partilhar os conteúdos por emaili e/ou redes sociais
  • Exportar os conteúdos em PDF
  • Adicionar comentários
  • Dar feedback à IBM
Naturalmente, também se espera que a IBM tire benefícios desta mudança. Entre os quais:
  • Ter todos os Infocenters numa única interface traduz-se em simplicidade e gestão mais eficiente
  • Os benefícios de introduzir uma nova funcionalidade ficarão disponíveis para todos os produtos e versões
  • Uma melhor interação com os clientes
Mas estas melhorias do lado da IBM acabam por se traduzir numa melhor experiência para o cliente durante a utilização da documentação online
A data para se descontinuar os InfoCenters ainda não está definida (tanto quanto julgo saber), mas sugiro que adopte desde já o Knowledge Center.. Verá que funciona mais rápido que os Infocenters e logo após a adaptação inicial, verá que é melhor que a interface antiga. Para mais detalhes, por favor consulte o webcast sobre este tema disponível na página dos Chat With the Labs, no seguinte endereço::

https://www.ibm.com/developerworks/community/wikis/home?lang=en_US#!/wiki/Informix+Chat+With+the+Lab/page/Welcome






Categorias: Blogs em Português

Heart bleed bug

Informix-Techonology - seg, 14/04/2014 - 11:41

This article is written in Englsih and Portuguese (original version here)
Este artigo está escrito em Inglês e Português (versão original aqui)



English version
If you have the slightest security concern, you've certainly noticed that everybody is very concerned with a security issue found on OpenSSL which was named Heart Bleed bug (a kind of joke because the bug attacks a functionality of SSL/TLS known as the Hearbeet extension). And this is perfectly justifiable. This has been considered one of the most serious security threat of the latest years. Why? Because it can be used to steal cryptographic sensitive information from sites like private keys. With these information the attackers can do all sorts of things, specifically impersonating those sites, and after that they can steal user information, passwords etc. The issue per si, is terrible, but the worst is that OpenSSL is used by many products, which means that this is not "just" a vendor specific bug.
Given the wide impact and seriousness of this issue most vendors hurried up to make sure if their products were affected or not by the bug. The OpenSSL versions affected by this bug are between 1.0.1 and 1.0.1f. Previous versions are safe (because they didn't implement the specific extension) and 1.0.1g includes the fix. So the question translate into: Do we use OpenSSL? And if yes, do we use an unsafe version?
In the immediate days after the public disclosure of this bug we received several questions from customers inquiring if our products were safe or not. And IBM hurried up to create alerts and information about this.
The relevant links are:


So, the good news is that Informix is safe from this bug. The SSL/TLS functionality used by Informix is provided by the generic IBM Global Security Kit which is not affected by the Heart Bleed bug




Versão Portuguesa
Se tem algum tipo de preocupação com a segurança, então certamente reparou que anda toda a gente preocupada com um problema de segurança descoberto no OpesSSL que recebeu o nome de Heart Bleed bug (uma piada ao facto de a falha estar na implementação de uma funcionalidade do SSL/TLS conhecida como extensão Heart Beet). E todo este alarme é perfeitamente compreensível e justificável. Este problema foi considerado como um dos mais sérios problemas de segurança descobertos nos últimos anos. Porquê? Porque pode ser usado para obtenção ilícita de informação sensível de criptografia, nomeadamente chaves privadas. Com esta informação os atacantes podem fazer uma série de coisas, entre as quais apresentarem-se como os sites legítimos e a partir daí obter informação sensível dos utilizadores como as suas palavras-chave. O assunto por si só já é terrível, mas o pior é que o OpenSSL é usado em muitos produtos de software, o que faz com que isto não esteja limitado a um fornecedor e/ou produto.
Dado o impacto alargado e a gravidade da falhar, a maioria dos vendedores apressaram-se a verificar se os seus produtos estariam afetados pelo problema. As versões do OpenSSL afetadas são as 1.0.1 até à 1.0.1.f. As versões anteriores não são afetadas pois não implementavam a extensão em causa, e a 1.0.1g incluí a correção para o problema. Portanto a questão resume-se a: Usamos OpenSSL? Se sim, usamos uma versão afetada?
Nos dias imediatamente a seguir aos anúncios públicos desta falha, recebemos várias questões de clientes a interrogarem-se se os nossos produtos estavam a salvo ou não deste bug. E a IBM apressou-se a criar alertas com informação sobre o tema. Os endereços relevantes para se entender o tema são:

Portanto, as boas notícias é que o Informix está a salvo desta falha de segurança. A funcionalidade SSL/TLS usada pelo Informix é fornecida pelo IBM  Global Security Kit, que não foi afetado pelo bug Heart Bleed.

Categorias: Blogs em Português

50(!) years of IBM Mainframe / 50(!) anos do Mainframe da IBM

Informix-Techonology - seg, 07/04/2014 - 08:07

This article is written in English and Portuguese (original version here)
Este artigo está escrito em Inglês e Português (versão original aqui)



English version:

You know that I rarely (if ever) post any information that does not relate to Informix here. In particular I don't use the blog to promote IBM or IBM products. But I believe this event is above all that. I'm not sure if anyone can define the birth date of a system (do you consider the first announcement, the start of the project, the first sale...?), but officially IBM Mainframe turns 50 today.
That's half a century... I'd say it's impossible to find another system that can match that (age) and that is still alive... I'm sure that the vast majority of my readers never worked with/in a mainframe. But I'd bet all of you have used a service based on a Mainframe today. We tend to think it belongs to an ancient world, where no innovation takes place etc. But, although I'm totally ignorant in regards to that environment one thing I can assure you: Many, or nearly most of the things we use today in LUW came from the mainframe. And if you think that was the case, but not anymore, let me just remind you that the first implementation of the blink project was on the mainframe. Today you can use it in Informix's Warehouse Accelerator and IBM DB2 Blu. If they keep the pace, I'm sure that what we'll use tomorrow is being developed there today. Just for fun, you may want to check tomorrow's live event about the next 50 years of the Mainframe.
And to close this in a way that's not offtopic, I can also remind you that Informix runs on zLinux :)


Versão Portuguesa:

É sabido que eu raramente (se é que aconteceu) escrevo aqui alguma coisa que não esteja relacionado com o Informix. Não uso o blog para promover a IBM ou outros produtos da IBM. Mas acredito que este evento está acima de tudo isso. Não sei se será possível a alguém definir a data de nascimento de um sistema (considera-se o primeiro anúncio, o início do projeto, a primeira venda...?), mas oficialmente o Mainframe da IBM faz 50 anos hoje.
É meio século... Diria que é impossível encontrar outro sistema com esta idade e que esteja ainda vivo... Apostaria que a maioria dos meus leitores nunca trabalharam com ou num Mainframe. Mas tenho a certeza que todos usaram um serviço baseado em Mainframe hoje. Temos tendência para pensar que o Mainframe pertence a um mundo antigo, onde não existe inovação etc. Mas apesar de ser totalmente ignorante sobre este ambiente há algo que posso assegurar: Muitas das coisas que usamos hoje em LUW (Linux/Unix/Windows) vieram do Mainframe. E se pensa o contrário, ou que tal já não acontece basta relembrar que a primeira implementação do projecto blink foi no Mainframe. Hoje pode usá-lo no Informix Warehouse Accelerator e no IBM DB2 Blu.
Se mantiverem o ritmo certamente o que usaremos amanhã é o que está lá a ser implementado hoje.
Para saber mais, nem que seja por curiosidade, poderá assistir amanhã a um evento na web sobre os próximos 50 anos do Mainframe.
E para encerrar o artigo de forma não off-topic, posso relembrar que o Informix corre em zLinux :)

Categorias: Blogs em Português

The importance of the OLAP functions / A importância das funções OLAP

Informix-Techonology - seg, 31/03/2014 - 19:36

This article is written in English and Portuguese (original version aqui)
Este artigo está escrito em Inglês e Português (versão original aqui)


English version:

Introduction
In a recent customer real situation I was faced with an optimization problem: They had a query that run in a certain time (let's assume around 1H), but they noticed that they were getting "duplicates" (not exactly a row duplicate, but records with duplicate values that should "identify" each row).
This happened because the data model was created in a way that allowed each "object" to be represented more than once in the table (assume different "lives" of the object). And they wanted to remove those "duplicates". Problem was that when they rewrote the query, it would take several hours (it never completed...).
Although I shouldn't use the exact data model, due to privacy restrictions, I'll try to create an equivalent model so that I can show you an example. Let's assume this table structure:
CREATE TABLE customer_tax_code
(
customer_num INTEGER, -- Customer identification
customer_plan CHAR(5), -- Customers are using some plan (INDividual, COMPany, MULTI etc. at each moment, but they may change)
customer_service SMALLINT, -- Customers may have several services active
customer_seq_id SERIAL, -- Each time a customer, in a specific plan, changes to another tax_code, this sequential_id is incremented
customer_tax_code SMALLINT -- The tax code which defines how the customer will be taxed for a specific service. Different tax_codes may contain different discount levels, bonus or promotions etc.
);
The basic query was really something like:
SELECT
customer_num, customer_plan, customer_seq_id, customer_tax_code
FROM
customer_tax_code
WHERE
customer_service = 1;

Note that I didn't consider any indexes. But this query would be very simple and would have only two possible query plans:

  1. A full scan if the number of records with customer_service = 1 was the majority of the table
  2. Some index containing customer_service, if the histograms showed that the percentage of records with customer_service = 1 was relatively low
The table had around 100M records. And the result set would contain several records for the same customer_num, customer_plan. These would be considered duplicates as for each pair customer_num/customer_plan they would want just the "last" record. Note that if a customer ever changed his plan they would like to collect all previous history for the customer while he changed plans, but inside a plan they would want just the "last" record. In other words, they would want the data for the records with MAX(customer_seq_id) for each pair customer_num/customer_plan. But because they also wanted the customer_tax_code field, a simple MAX() and GROUP BY would not work (we'd be forced to include all the non aggregate fields in the projection clause - or select list - in the GROUP BY, and that would destroy the purpose).

The solution they had was roughly this:
SELECT
customer_num, customer_plan, customer_seq_id, customer_tax_code
FROM
customer_tax_code a
WHERE
a.customer_service = 1 AND
a.customer_seq_id = (SELECT
MAX(b.customer_seq_id)
FROM
customer_tax_code b
WHERE
b.customer_num = a.customer_num AND
b.customer_plan = a.customer_plan AND
b.customer_service = 1
);
This would give the desired result.... But a typical query plan would include the options above (full scan or index scan based on customer_service) and for each record would require an index access for the sub-query.
To give you some perspective, the percentage of records with customer_service = 1 was roughly 41% of the table. Around 41M rows.
An index like one of these would help a lot:
  • (customer_num, customer_plan, customer_num_seq_id)
    Would provide the easy access to MAX() for customer_num,customer_plan
  • (customer_num, customer_plan, customer_service, customer_num_seq_id)
    Could improve on the one before as it would include all the filters in the sub-query
  • (customer_num, customer_plan)
    Basic index to easily access all rowids for customer_num/customer_plan pairs
But in any case, this would normally do a full scan and 41M index accesses. And this is painful. I tried several variations like creating an inline view with (customer_num, customer_plan, MAX(customer_seq_id) ... WHERE customer_service = 1 GROUP BY 1, 2) and then join this with the table. Depending on index manipulation, some hints to force a specific JOINS (like HASH JOIN) etc. I was able to achieve a query that would take 2H-3H. Even so, it was a bit awkward, slow and would require hints etc.

Solution
Then I remembered that we introduced the so called OLAP functions in 12.10. And actually one of them provides what we need for this sort of queries (need to find a MAX(), but we require aditional fields besides that one and the ones we're going to base our GROUP BY).
In fact we can generalize this problem (and hopefully also the solution), as this happens every time the data model is created in a way that maintains "history" and we just want the last image of the object for each "state". In other words, all the situations where besides other filters we want the records where:
some_field = MAX(some_field) and the group for the MAX calculation is defined by a set of columns. Many times "some_field" is a date/datetime field and the set defines an object identification.

The function in question is RANK(). It basically calculates a rank of a field value within a "window" defined in another one or more fields (the ones we'd use for GROUP BY in MAX() calculation).

In our case we can get a "rank" of customer_seq_id for each group of customer_num/customer_plan. By default, the record with the lower (in terms of ORDER BY depending on the datatype) customer_seq_id would get a rank = 1. The next one would get 2 and so on. But we can do the opposite. Assign 1 to the record with the highest customer_seq_id, 2 the the one right below that one and so on.

The query would look like:
SELECT
customer_num, customer_plan, customer_seq_id, customer_tax_code,
RANK() OVER (PARTITION BY customer_num, customer_plan ORDER BY customer_seq_id DESC) myrank
FROM
customer_tax_code a
WHERE
a.customer_service = 1
Note this would not eliminate the "duplicates". It would just "rank" the result set. Actually we need to impose a restriction saying we just want the records that have myrank = 1 (these will be the ones with higher customer_seq_is within the group of records with same customer_num and customer_plan),

Because the RANK() is calculated "in the result set", it doesn't allow a condition "myrank = 1" within the WHERE clause.
But ideally we would be able to use the "HAVING clause" like this:
... HAVING myrank = 1
Unfortunately this raises error -217 (column "myrank" not found). We can't use column alias in the HAVING clause (we can for GROUP BY and ORDER BY clauses). Additionally we cannot use the full column expression in the HAVING clause as it will raise the error:
-25856  Invalid usage of window aggregate in this context.

You can use OLAP window aggregate functions in the Projection clause
and in the ORDER BY clause of queries.  They cannot be present in
the WHERE clause or in the HAVING clause (except within a subquery).
So the way to do that is to wrap the query above as an inline view for an outside query that includes the restriction myrank = 1:
SELECT
*
FROM
(
SELECT
customer_num, customer_plan, customer_seq_id, customer_tax_code,
RANK() OVER (PARTITION BY customer_num, customer_plan ORDER customer_seq_id DESC) myrank
FROM
customer_tax_code a
WHERE
a.customer_service = 1
)
WHERE my_rank = 1;
This is executed with a single full scan on the table (around 50m). And doesn't depend on hints, complex queries etc. We just take advantage of the new functions. It was the fastest query plan we could achieve for this query.
Only one issue: Customer production environment is still in 11.50, which doesn't contain these functions. Just an example of how customers loose time and opportunity by sticking to old versions...

To give you a better insight about this problem, I created a test case with some "random" test data that can prove the point.  You may apply it to your environment as I'm testing in a VM and these queries are heavily I/O bound... and I/O is not the best aspect of virtual machines.

Test case
The test data was created with an GAWK script which tries to mimic the original conditions. The script can be found at the bottom of the article. I generated 2.5M rows:
castelo@primary:informix-> echo 2500000 | awk -f generate_data.awk > rank_test_data.unl
castelo@primary:informix->
Then I loaded the data into a table with same schema as above:
castelo@primary:informix-> dbaccess -e stores test_ddl.sql

Database selected.

DROP TABLE IF EXISTS customer_tax_code;
Table dropped.


CREATE RAW TABLE customer_tax_code
(
customer_num INTEGER, -- Customer identification
customer_plan CHAR(5), -- Customers are using some plan (INDividual, COMPany, MULTI etc. at each moment, but they may change)
customer_service SMALLINT, -- Customers may have several services active
customer_seq_id SERIAL, -- Each time a customer, in a specific plan, changes to another tax_code, this sequential_id is incremented
customer_tax_code SMALLINT -- The tax code which defines how the customer will be taxed for a specific service. Different tax_codes may contain different discount levels, bonus or promotions etc.
) IN dbs1
EXTENT SIZE 10000 NEXT SIZE 10000
LOCK MODE ROW;
Table created.



BEGIN WORK;
Started transaction.


LOCK TABLE customer_tax_code IN EXCLUSIVE MODE;
Table locked.


LOAD FROM rank_test_data.unl INSERT INTO customer_tax_code;
2500000 row(s) loaded.


COMMIT WORK;
Data committed.


ALTER TABLE customer_tax_code TYPE(STANDARD);
Table altered.


CREATE INDEX ix_customer_tax_code_1 ON customer_tax_code(customer_num,customer_plan,customer_seq_id) IN dbs2;
Index created.


CREATE INDEX ix_customer_tax_code_2 ON customer_tax_code(customer_service) IN dbs2;
Index created.



Database closed.

castelo@primary:informix->
And then I run the following query to have an idea of how data was generated:
castelo@primary:informix-> dbaccess -e stores distrib.sql

Database selected.

SELECT
COUNT(*) num, TRUNC((COUNT(*) / 2500000) * 100, 2) percent
FROM
customer_tax_code
WHERE
customer_service = 1;

num percent

1025501 41.02

1 row(s) retrieved.


SELECT
COUNT(*) num, TRUNC((COUNT(*) / 2500000) * 100, 2) percent
FROM (
SELECT
DISTINCT customer_num, customer_plan
FROM
customer_tax_code
WHERE
customer_service = 1
);

num percent

798315 31.93

1 row(s) retrieved.



Database closed.

castelo@primary:informix->
So we should expect a result set with 798315 rows. And we can see that we have 41% of the table data with customer_service = 1 (just like in the real customer situation)
In order to show the difference in behavior between both queries I've used a script which I created some time ago and  is documented in this article. For that I prepared an SQL script for each query:
castelo@primary:informix-> cat test_query_1.sql
SET EXPLAIN FILE TO "query_option_1.txt";
SET EXPLAIN ON;
UNLOAD TO /dev/null
SELECT
customer_num, customer_plan, customer_seq_id, customer_tax_code
FROM
customer_tax_code a
WHERE
a.customer_service = 1 AND
a.customer_seq_id = (SELECT MAX(b.customer_seq_id) FROM customer_tax_code b WHERE
b.customer_num = a.customer_num AND
b.customer_plan = a.customer_plan AND
b.customer_service = 1
);
castelo@primary:informix->
castelo@primary:informix-> cat test_query_2.sql
SET EXPLAIN FILE TO "query_option_2.txt";
SET EXPLAIN ON;
UNLOAD TO /dev/null
SELECT
*
FROM (
SELECT
customer_num, customer_plan, customer_seq_id, customer_tax_code,
RANK() OVER (PARTITION BY customer_num, customer_plan ORDER BY customer_seq_id DESC) myrank
FROM
customer_tax_code a
WHERE
a.customer_service = 1
)
WHERE myrank = 1;
castelo@primary:informix->
Note that I've turned on the EXPLAIN and that I'm UNLOADING to /dev/null. This is just a trick to avoid wasting time writing the data to a file and by doing so, I minimize the variables involved that can twist the query processing time. I'd also like to point out that the issue I'm trying to show is basically an I/O bound problem. And because I've just loaded the data, created the indexes and run the queries above, the side effect is that at this point I had a significant portion of the table in the engine cache. To avoid the influence of this in the times, I've restarted the instance (onmode -ky/oninit).

Then I run the mentioned scripts:
castelo@primary:informix-> onmode -ky;oninit
castelo@primary:informix-> /usr/informix/bin/ixprofiling -w -i stores test_query_1.sql

Database selected.

Engine statistics RESETed. Query results:
Query start time: 09:51:29.634374000

SET EXPLAIN FILE TO "query_option_1.txt";
Explain set.


SET EXPLAIN ON;
Explain set.


UNLOAD TO /dev/null
SELECT
customer_num, customer_plan, customer_seq_id, customer_tax_code
FROM
customer_tax_code a
WHERE
a.customer_service = 1 AND
a.customer_seq_id = (SELECT MAX(b.customer_seq_id) FROM customer_tax_code b WHERE
b.customer_num = a.customer_num AND
b.customer_plan = a.customer_plan AND
b.customer_service = 1
);
798315 row(s) unloaded.

Query stop time: 09:56:16.976229000

Thread profiles (SID: 5)
LkReq LkWai DLks TOuts LgRec IsRd IsWrt IsRWr IsDel BfRd BfWrt LgUse LgMax SeqSc Srts DskSr SrtMx Sched CPU Time Name
----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----------- ------------
496e4 0 0 0 0 831e3 0 0 0 493e4 0 0 0 2 0 0 0 86801 26.71790603 sqlexec
LkWs IOWs nIOW IdxBR Name
------------ ------------ ------------ ------------ -----------------------------------
0.0 251.79098566 16543 0 sqlexec


Session wait statistics:
Thread name Condition Num. waits Cum. time Max wait
----------------------------------- ----------------------------------- ------------ ------------ ------------
sqlexec mutex 1 23.611186280 23.0
sqlexec mt yield 1 6508.7673752 6508.0
sqlexec buffer 17 462525.80710 61764.0
sqlexec mt yield 0 3929 611561.28945 14680.0
sqlexec mt ready 86801 799028.07279 14682.0
sqlexec mt yield n 60 1104496.3218 58390.0
sqlexec condition 2935 2236864.5528 16698.0
sqlexec running 23461 26712075.663 53739.0
sqlexec aio 16517 251627122.20 3787185.0
sqlexec Total time 283560206.29

Partitions profiles (Database: stores)
LkReq LkWai DLks TOuts DskRd DskWr IsRd IsWrt IsRWr IsDel BfRd BfWrt SeqSc Object name
----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ------------------------------------------------------
[... SOME systables stats were removed for clarity ...]
332e4 0 0 0 26049 0 17641 0 0 0 909e3 0 1 customer_tax_code
164e4 0 0 0 30852 0 163e4 0 0 0 413e4 0 0 customer_tax_code#ix_customer_tax_code_1



Database closed.


real 4m49.367s
user 0m0.390s
sys 0m1.390s

castelo@primary:informix-> cat query_option_1.txt

QUERY: (OPTIMIZATION TIMESTAMP: 03-31-2014 09:51:29)
------
SELECT
customer_num, customer_plan, customer_seq_id, customer_tax_code
FROM
customer_tax_code a
WHERE
a.customer_service = 1 AND
a.customer_seq_id = (SELECT MAX(b.customer_seq_id) FROM customer_tax_code b WHERE
b.customer_num = a.customer_num AND
b.customer_plan = a.customer_plan AND
b.customer_service = 1
)

Estimated Cost: 2418832
Estimated # of Rows Returned: 512750

1) informix.a: SEQUENTIAL SCAN

Filters: (informix.a.customer_service = 1 AND informix.a.customer_seq_id = <subquery> )

Subquery:
---------
Estimated Cost: 4
Estimated # of Rows Returned: 1

1) informix.b: INDEX PATH

Filters: informix.b.customer_service = 1

(1) Index Name: informix.ix_customer_tax_code_1
Index Keys: customer_num customer_plan customer_seq_id (Reverse) (Aggregate) (Serial, fragments: ALL)
Lower Index Filter: (informix.b.customer_num = informix.a.customer_num AND informix.b.customer_plan = informix.a.customer_plan )



Query statistics:
-----------------

Table map :
----------------------------
Internal name Table name
----------------------------
t1 a

type table rows_prod est_rows rows_scan time est_cost
-------------------------------------------------------------------
scan t1 798315 512750 2500000 04:38.22 2418832


Subquery statistics:
--------------------

Table map :
----------------------------
Internal name Table name
----------------------------
t1 b

type table rows_prod est_rows rows_scan time est_cost
-------------------------------------------------------------------
scan t1 806330 1 821121 04:30.70 5

type rows_prod est_rows rows_cons time
-------------------------------------------------
group 806330 1 806330 04:31.89

castelo@primary:informix->
Some facts about this execution:
  • It took around 5m
  • It did a sequential scan on the table and used the most complex index for the sub-query (this ones provides easy access to the MAX(customer_num_seq_id) by customer_num/customer_plan
  • It spend most of the time in I/O wait
And then the second query:
castelo@primary:informix-> onmode -ky;oninit
castelo@primary:informix-> /usr/informix/bin/ixprofiling -w -i stores test_query_2.sql

Database selected.

Engine statistics RESETed. Query results:
Query start time: 09:58:23.477004000

SET EXPLAIN FILE TO "query_option_2.txt";
Explain set.


SET EXPLAIN ON;
Explain set.


UNLOAD TO /dev/null
SELECT
*
FROM (
SELECT
customer_num, customer_plan, customer_seq_id, customer_tax_code,
RANK() OVER (PARTITION BY customer_num, customer_plan ORDER BY customer_seq_id DESC) myrank
FROM
customer_tax_code a
WHERE
a.customer_service = 1
)
WHERE myrank = 1;
798315 row(s) unloaded.

Query stop time: 09:59:59.676333000

Thread profiles (SID: 6)
LkReq LkWai DLks TOuts LgRec IsRd IsWrt IsRWr IsDel BfRd BfWrt LgUse LgMax SeqSc Srts DskSr SrtMx Sched CPU Time Name
----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----------- ------------
250e4 0 0 0 0 19698 102e4 0 0 206e3 3997 0 0 3 1 1 18944 108e3 9.918404867 sqlexec
LkWs IOWs nIOW IdxBR Name
------------ ------------ ------------ ------------ -----------------------------------
0.0 74.245603123 3087 0 sqlexec


Session wait statistics:
Thread name Condition Num. waits Cum. time Max wait
----------------------------------- ----------------------------------- ------------ ------------ ------------
sqlexec mutex 6 76.625269575 19.0
sqlexec sort io 280 641848.77478 94357.0
sqlexec condition 4554 2177075.9388 21014.0
sqlexec mt yield 0 9538 2845631.3908 24543.0
sqlexec mt ready 108699 2931312.6256 24544.0
sqlexec buffer 191 5480268.6835 126467.0
sqlexec running 17658 9915220.0648 47373.0
sqlexec aio 3088 74141204.005 451378.0
sqlexec Total time 98132638.109

Partitions profiles (Database: stores)
LkReq LkWai DLks TOuts DskRd DskWr IsRd IsWrt IsRWr IsDel BfRd BfWrt SeqSc Object name
----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ------------------------------------------------------
[... SOME systables stats were removed for clarity ...]
250e4 0 0 0 26049 358 10154 0 0 0 179e3 3220 1 customer_tax_code



Database closed.


real 1m38.444s
user 0m0.140s
sys 0m0.750s
castelo@primary:informix->
QUERY: (OPTIMIZATION TIMESTAMP: 03-31-2014 09:58:23)
------
SELECT
*
FROM (
SELECT
customer_num, customer_plan, customer_seq_id, customer_tax_code,
RANK() OVER (PARTITION BY customer_num, customer_plan ORDER BY customer_seq_id DESC) myrank
FROM
customer_tax_code a
WHERE
a.customer_service = 1
)
WHERE myrank = 1

Estimated Cost: 42278
Estimated # of Rows Returned: 102550

1) (Temp Table For Collection Subquery): SEQUENTIAL SCAN

Filters: (Temp Table For Collection Subquery).myrank = 1


Query statistics:
-----------------

Table map :
----------------------------
Internal name Table name
----------------------------
t1 a
t2 (Temp Table For Collection Subquery)

type table rows_prod est_rows rows_scan time est_cost
-------------------------------------------------------------------
scan t1 1025501 1025501 2500000 00:07.19 101043

type rows_sort est_rows rows_cons time
-------------------------------------------------
sort 1025501 0 1025501 01:27.73

type it_count time
----------------------------
olap 1025501 01:31.31

type table rows_prod est_rows rows_scan time est_cost
-------------------------------------------------------------------
scan t2 798315 102550 1025501 00:00.47 42278

castelo@primary:informix->
Some facts about this execution:
  1. The query took around 1m40s
  2. It did the same sequential scan as the one aboive
  3. It didn't require heavy I/O on any index
  4. It wasted little more than 1m on I/O wait
Summary
This table summarizes the differences between both queries:
table.tableizer-table { border: 1px solid #CCC; } .tableizer-table td { padding: 4px; margin: 3px; border: 1px solid #ccc; } .tableizer-table th { background-color: #104E8B; color: #FFF; font-weight: bold; }
Query 1 (sub-query)Query 2 (OLAP)Execution time4m49.367s1m38.444sThread ISAM reads831000,00019698,000Thread ISAM writes0,0001020000,000Thread buffer reads4930000,000206000,000Thread buffer writes0,0003997,000Thread SortMax0,00018944,000Thread schedules86801,000108000,000Thread CPU time26,7189,918Table ISAM reads17641,00010154,000Table buffer reads909000,000179000,000Table buffer writes0,0003220,000Table disk reads26049,00026049,000Table disk writes0,000358,000Index ISAM reads1630000,0000,000Index buffer reads4130000,0000,000Index disk reads30852,0000,000I/O waits16543,0003087,000I/O wait time251,79074,245
As you can see for yourself, the second query has much better values in most common aspects. It does take extra work for sorting and writing the temporary structure. But even so, this seems to have much less impact. There is also another aspect that makes the first option much better in the test data than in the real data. This aspect alone could deserve a dedicated article, but for now I'll just point out that the way the data was created makes the table relatively ordered by customer_num/customer_plan. This is not an irrelevant aspect in the real situation because of these two reasons:
  1. In the real scenario, the index used in the sub-query only contained customer_num, customer_plan, and did not contain customer_seq_id which in my test allows the engine to solve the MAX() with just the index access
  2. When we access the index for the sub-query, the rowids we get are "close". In most cases a single disk access will be able to fetch all of them. In the real situation, the records for the same customer_num/customer_plan would require more disk accesses. Even more, since we're doing a sequential scan, when we try to fetch the row data for the records with the same customer_num/customer_plan, chances are that those pages are already place in memory (retrieved by the sequential scan). If the table was not "ordered" this would not happen and the disk I/O impact would be even bigger.
As a final note, I don't see why we can't use a column alias in the HAVING clause. If there is a good reason I can't figure it out. And I'm not sure if we couldn't optimize the query even more if we were able to state we would only want one record. For this reason I inserted a new request for enhancement (RFE) on the public site. The feature request URL is: http://www.ibm.com/developerworks/rfe/execute?use_case=viewRfe&CR_ID=46670


Versão Portuguesa:

Introducão
Numa situação real num cliente, fui confrontado com um problema de otimização: Usavam uma query que demorava cerca de 1H a correr, mas notaram que a mesma gerava "duplicados" (não exatamente duplicados, mas registos repetidos em valores que deveriam identificar um objeto).
Isto acontecia porque o modelo de dados foi criado de forma a permitir que cada "objeto" fosse representado mais que uma vez na tabela (assumia diferentes "vidas"). E o objetivo era remover esses "duplicados" do resultado da query. O problema é que quando re-escreveram a query o tempo de execução passou para várias horas (nunca terminou...)
Não posso usar o modelo de dados exato por questões de privacidade, mas vou tentar criar um modelo que seja funcionalmente equivalente, para que possa exemplificar a situação. Vamos assumir esta estrutura de tabela:
CREATE TABLE customer_tax_code
(
customer_num INTEGER, -- Identificação do cliente
customer_plan CHAR(5), -- Cada cliente está num determinado plano (INDividual, COMPany, MULTI etc.) em cada momento, mas podem mudar
customer_service SMALLINT, -- Os clientes podem ter vários serviços activos
customer_seq_id SERIAL, -- Cada vez que um cliente muda de código de taxação é criado um novo registo com um número de sequência novo
customer_tax_code SMALLINT -- O código de taxação que define a regra de taxação para o serviço
);
A query original era algo como:
SELECT
customer_num, customer_plan, customer_seq_id, customer_tax_code
FROM
customer_tax_code
WHERE
customer_service = 1;
Repare que não considerei ainda quaisquer índices. Mas esta query é extremamente simples e só teria dois planos de execução possíveis:
  1. Um scan total da tabela se o número de registos com customer_service = 1 for uma percentagem significativa da tabela
  2. Algum índice que contenha o campo customer_service, se os histogramas mostrarem que a percentagem de registos que valida a condição for reduzido (face à dimensão da tabela)
A tabela tinha cerca de 100M de registos. E o resultado continha vários registos para o mesmo par customer_num/customer_plan. Estes seriam considerados duplicados, pois para cada par apenas queriam o "último" registo. Repare que se o cliente mudasse de plano o objetivo da query era trazer todas as "vidas" (diferentes planos), mas para cada plano apenas queriam a última entrada.
Por outras palavras queriam trazer todos os registos onde o customer_seq_id era o MAX(customer_seq_id) para cada par customer_num/customer_plan (com customer_service=1)
Mas como querem obter também outros dados da linha, o uso do MAX(customer_seq_id) e o respetivo GROUP BY customer_num, customer_plan não pode ser usado (seríamos obrigado a introduzir todos os campos sem função de agregação na cláusula GROUP BY, e isso iria alterar o resultado obtido).

A solução que tinham criado era isto, em traços gerais:
SELECT
customer_num, customer_plan, customer_seq_id, customer_tax_code
FROM
customer_tax_code a
WHERE
a.customer_service = 1 AND
a.customer_seq_id = (SELECT
MAX(b.customer_seq_id)
FROM
customer_tax_code b
WHERE
b.customer_num = a.customer_num AND
b.customer_plan = a.customer_plan AND
b.customer_service = 1
);
Esta query obtém o resultado esperado... Mas o plano de execução típico incluíria as opções acima (scan completo ou acesso por índice sobre a coluna customer_service), e para cada registo obtido teríamos de fazer um acesso indexado para resolver a sub-query.
Para fornecer mais algum contexto, a percentagem de registos com customer_service = 1 rondava os 41% da tabela (cerca de 41M de linhas).
Um índice semelhante a um destes ajuda bastante a query:
  • (customer_num, customer_plan, customer_num_seq_id)
    Fornece um acesso rápido ao MAX() por customer_num, customer_plan
  • (customer_num, customer_plan, customer_service, customer_num_seq_id)
    Poderia melhorar o anterior pois incluí todos os filtros para a sub-query
  • (customer_num, customer_plan)
    Este seria o índice mais básico para aceder aos rowids das linhas para cada para customer_num/customer_plan
Mas no caso atual, isto faria normalmente um scan completo e depois 41M de acessos a um dos índices. E isto é penoso. Tentei várias variantes como criar uma inline view com (customer_num, customer_plan, MAX(customer_seq_id)... WHERE customer_service = 1 GROUP BY 1,2), e depois fazer o JOIN disto com a tabela. Dependendo da manipulação dos índices, de HINTS para forçar determinadas opções (HASH JOIN) etc. fui capaz de obter um plano de execução que demorava 2H-3H. Mas mesmo assim, era bastante "forçado", demorado e necessitava de hints.

Solução
Nesta altura lembrei-me que introduzimos as chamadas funções OLAP na versão 12.10. E na verdade uma delas dá-nos algo que necessitamos para este tipo de queries (necessidade de encontrar um MAX(), mas necessitamos de trazer mais campos para além dos que usaríamos para o GROUP BY).
Na verdade, este problema pode ser generalizado (e a ideia é que a solução também), dado que isto acontece sempre que o modelo de dados é criado de forma que mantenha histórico e nós só pretendemos o último estado de cada objeto. Por outras palavras, todas as situações onde para além de outros filtros, queremos:
campo_xpto = MAX(campo_xpto) e o grupo para o cálculo do máximo é definido por um conjunto de colunas. Muitas vezes o "campo_xpto" é um date/datetime e o conjunto de colunas define um identificador de objeto.

A função em questão é o RANK(). Sumariamente, calcula uma ordem para um campo, numa "janela" definida por um ou mais campos (os que usaríamos no GROUP BY do cálculo do MAX()).

No nosso caso, podemos obter um "rank" do customer_seq_id, para cada grupo/janela definido pelos campos customer_num/customer_plan.
Por omissão o registo com o campo com valor mais baixo (em termos de ordenação conforme o tipo de dados) no customer_seq_id recebería o rank() = 1. O seguinte receberia o rank 2 e assim por diante. Mas podemos fazer o oposto. Atribuir 1 ao registo com o customer_seq_id mais alto, 2 ao que se encontrar logo abaixo etc.

A query ficaria assim:
SELECT
customer_num, customer_plan, customer_seq_id, customer_tax_code,
RANK() OVER (PARTITION BY customer_num, customer_plan ORDER BY customer_seq_id DESC) myrank
FROM
customer_tax_code a
WHERE
a.customer_service = 1
Note que isto não eliminaria os "duplicados". Apenas classificaria os registos no conjunto resultante da query. Na verdade temos de impor uma restrição, dizendo que apenas queremos os campos cujo myrank = 1 (estes serão os registos com o customer_seq_id mais alto, dentro do grupo de registos com os mesmos customer_num e customer_plan).

Dado que o RANK() é calculado sobre o conjunto de resultados, não pode aceitar o uso da condição "murank = 1" na cláusula WHERE.
Mas idealmente poderíamos usar a cláusula HAVING, tal como:
... HAVING myrank = 1
Infelizmente isto dispara o erro -217 (column "myrank" not found). Não podemos usar alias de colunas na cláusula HAVING (podemos usá-las nos GROUP BY e ORDER BY).
E também não podemos usar a expressão completa na cláusula HAVING pois isso causa o erro:
-25856  Invalid usage of window aggregate in this context.

You can use OLAP window aggregate functions in the Projection clause
and in the ORDER BY clause of queries.  They cannot be present in
the WHERE clause or in the HAVING clause (except within a subquery).
Por isso, o que temos de fazer é "embrulhar" a query acima como uma inline view numa query exterior que contenha a restrição myrank = 1:
SELECT
*
FROM
(
SELECT
customer_num, customer_plan, customer_seq_id, customer_tax_code,
RANK() OVER (PARTITION BY customer_num, customer_plan ORDER customer_seq_id DESC) myrank
FROM
customer_tax_code a
WHERE
a.customer_service = 1
)
WHERE my_rank = 1;
Esta query era executada com um scan completo da tabela e terminava em cerca de 50m. E é simples de escrever e entender, não dependendo de hints, queries complexas, sub-queries etc. Apenas tiramos proveito das novas funções. Este foi o plano de execução mais eficiente que consegui para esta situação.
Apenas tive um problema: Os sistemas de produção do cliente ainda estão em 11.50, a qual não contém estas funções. Serve como exemplo de como os clientes perdem tempo e perdem oportunidades ao manterem-se em versões antigas....

Para lhe dar uma melhor perceção sobre este problema, criei um caso de teste com dados de teste "aleatórios", de forma a provar e demonstrar o princípio explicado acima. Poderá aplicar isto ao seu ambiente, dado que estou a testar num ambiente virtualizado, e estas queries sofrerem muito com o I/O... e é sabido que o I/O não é o ponto forte dos sistemas virtualizados.

Caso de teste
Os dados de teste foram criados com um script GAWK que tenta imitar as condições originais que descrevi acima. O script pode ser consultado no final do artigo. Gerei 2.5M de linhas:
castelo@primary:informix-> echo 2500000 | awk -f generate_data.awk > rank_test_data.unl
castelo@primary:informix->
Depois carreguei os dados numa tabela com a estrutura já mostrada acima:
castelo@primary:informix-> dbaccess -e stores test_ddl.sql

Database selected.

DROP TABLE IF EXISTS customer_tax_code;
Table dropped.


CREATE RAW TABLE customer_tax_code
(
customer_num INTEGER, -- Identificação do cliente
customer_plan CHAR(5), -- Cada cliente está num determinado plano (INDividual, COMPany, MULTI etc.) em cada momento, mas podem mudar
customer_service SMALLINT, -- Os clientes podem ter vários serviços activos
customer_seq_id SERIAL, -- Cada vez que um cliente muda de código de taxação é criado um novo registo com um número de sequência novo
customer_tax_code SMALLINT -- O código de taxação que define a regra de taxação para o serviço
) IN dbs1
EXTENT SIZE 10000 NEXT SIZE 10000
LOCK MODE ROW;
Table created.



BEGIN WORK;
Started transaction.


LOCK TABLE customer_tax_code IN EXCLUSIVE MODE;
Table locked.


LOAD FROM rank_test_data.unl INSERT INTO customer_tax_code;
2500000 row(s) loaded.


COMMIT WORK;
Data committed.


ALTER TABLE customer_tax_code TYPE(STANDARD);
Table altered.


CREATE INDEX ix_customer_tax_code_1 ON customer_tax_code(customer_num,customer_plan,customer_seq_id) IN dbs2;
Index created.


CREATE INDEX ix_customer_tax_code_2 ON customer_tax_code(customer_service) IN dbs2;
Index created.



Database closed.

castelo@primary:informix->
A query seguinte permite ter uma idea de como os dados foram gerados:
castelo@primary:informix-> dbaccess -e stores distrib.sql

Database selected.

SELECT
COUNT(*) num, TRUNC((COUNT(*) / 2500000) * 100, 2) percent
FROM
customer_tax_code
WHERE
customer_service = 1;

num percent

1025501 41.02

1 row(s) retrieved.


SELECT
COUNT(*) num, TRUNC((COUNT(*) / 2500000) * 100, 2) percent
FROM (
SELECT
DISTINCT customer_num, customer_plan
FROM
customer_tax_code
WHERE
customer_service = 1
);

num percent

798315 31.93

1 row(s) retrieved.



Database closed.

castelo@primary:informix->
Portanto deveremos esperar um conhjunto de resultados com 798315 linhas. E como podemos ver, os dados da tabela de teste têm 41% de registos com customer_service = 1 (tal como na situação real).
Para mostrar a diferença de comportamento entre ambas as queries usei um script que criei há algum tempo atrás e que documentei neste artigo. Para o usar preparei também um script para cada query:
castelo@primary:informix-> cat test_query_1.sql
SET EXPLAIN FILE TO "query_option_1.txt";
SET EXPLAIN ON;
UNLOAD TO /dev/null
SELECT
customer_num, customer_plan, customer_seq_id, customer_tax_code
FROM
customer_tax_code a
WHERE
a.customer_service = 1 AND
a.customer_seq_id = (SELECT MAX(b.customer_seq_id) FROM customer_tax_code b WHERE
b.customer_num = a.customer_num AND
b.customer_plan = a.customer_plan AND
b.customer_service = 1
);
castelo@primary:informix->
castelo@primary:informix-> cat test_query_2.sql
SET EXPLAIN FILE TO "query_option_2.txt";
SET EXPLAIN ON;
UNLOAD TO /dev/null
SELECT
*
FROM (
SELECT
customer_num, customer_plan, customer_seq_id, customer_tax_code,
RANK() OVER (PARTITION BY customer_num, customer_plan ORDER BY customer_seq_id DESC) myrank
FROM
customer_tax_code a
WHERE
a.customer_service = 1
)
WHERE myrank = 1;
castelo@primary:informix->
Repare que activei o EXPLAIN e que estou a fazer o UNLOAD para /dev/null. Isto é apenas um truque para evitar desperdiçar tempo a escrever os dados em ficheiro, e ao fazê-lo estou a minimizar as variáveis que afectarão o tempo de execução. Gostaria também de referir que o problema que estou a tentar explicar traduz-se num problema de I/O. E dado que acabei de carregar os dados, criar os índices e correr as queries acima, no ponto em que nos encontramos a memória do servidor de base de dados conterá uma parte significativa dos dados da tabela. Para evitar que isto influencie os tempos, re-iniciei a instância (onmode -ky;oninit).
Depois executei os referidos scripts:
castelo@primary:informix-> onmode -ky;oninit
castelo@primary:informix-> /usr/informix/bin/ixprofiling -w -i stores test_query_1.sql

Database selected.

Engine statistics RESETed. Query results:
Query start time: 09:51:29.634374000

SET EXPLAIN FILE TO "query_option_1.txt";
Explain set.


SET EXPLAIN ON;
Explain set.


UNLOAD TO /dev/null
SELECT
customer_num, customer_plan, customer_seq_id, customer_tax_code
FROM
customer_tax_code a
WHERE
a.customer_service = 1 AND
a.customer_seq_id = (SELECT MAX(b.customer_seq_id) FROM customer_tax_code b WHERE
b.customer_num = a.customer_num AND
b.customer_plan = a.customer_plan AND
b.customer_service = 1
);
798315 row(s) unloaded.

Query stop time: 09:56:16.976229000

Thread profiles (SID: 5)
LkReq LkWai DLks TOuts LgRec IsRd IsWrt IsRWr IsDel BfRd BfWrt LgUse LgMax SeqSc Srts DskSr SrtMx Sched CPU Time Name
----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----------- ------------
496e4 0 0 0 0 831e3 0 0 0 493e4 0 0 0 2 0 0 0 86801 26.71790603 sqlexec
LkWs IOWs nIOW IdxBR Name
------------ ------------ ------------ ------------ -----------------------------------
0.0 251.79098566 16543 0 sqlexec


Session wait statistics:
Thread name Condition Num. waits Cum. time Max wait
----------------------------------- ----------------------------------- ------------ ------------ ------------
sqlexec mutex 1 23.611186280 23.0
sqlexec mt yield 1 6508.7673752 6508.0
sqlexec buffer 17 462525.80710 61764.0
sqlexec mt yield 0 3929 611561.28945 14680.0
sqlexec mt ready 86801 799028.07279 14682.0
sqlexec mt yield n 60 1104496.3218 58390.0
sqlexec condition 2935 2236864.5528 16698.0
sqlexec running 23461 26712075.663 53739.0
sqlexec aio 16517 251627122.20 3787185.0
sqlexec Total time 283560206.29

Partitions profiles (Database: stores)
LkReq LkWai DLks TOuts DskRd DskWr IsRd IsWrt IsRWr IsDel BfRd BfWrt SeqSc Object name
----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ------------------------------------------------------
[... SOME systables stats were removed for clarity ...]
332e4 0 0 0 26049 0 17641 0 0 0 909e3 0 1 customer_tax_code
164e4 0 0 0 30852 0 163e4 0 0 0 413e4 0 0 customer_tax_code#ix_customer_tax_code_1



Database closed.


real 4m49.367s
user 0m0.390s
sys 0m1.390s

castelo@primary:informix-> cat query_option_1.txt

QUERY: (OPTIMIZATION TIMESTAMP: 03-31-2014 09:51:29)
------
SELECT
customer_num, customer_plan, customer_seq_id, customer_tax_code
FROM
customer_tax_code a
WHERE
a.customer_service = 1 AND
a.customer_seq_id = (SELECT MAX(b.customer_seq_id) FROM customer_tax_code b WHERE
b.customer_num = a.customer_num AND
b.customer_plan = a.customer_plan AND
b.customer_service = 1
)

Estimated Cost: 2418832
Estimated # of Rows Returned: 512750

1) informix.a: SEQUENTIAL SCAN

Filters: (informix.a.customer_service = 1 AND informix.a.customer_seq_id = <subquery> )

Subquery:
---------
Estimated Cost: 4
Estimated # of Rows Returned: 1

1) informix.b: INDEX PATH

Filters: informix.b.customer_service = 1

(1) Index Name: informix.ix_customer_tax_code_1
Index Keys: customer_num customer_plan customer_seq_id (Reverse) (Aggregate) (Serial, fragments: ALL)
Lower Index Filter: (informix.b.customer_num = informix.a.customer_num AND informix.b.customer_plan = informix.a.customer_plan )



Query statistics:
-----------------

Table map :
----------------------------
Internal name Table name
----------------------------
t1 a

type table rows_prod est_rows rows_scan time est_cost
-------------------------------------------------------------------
scan t1 798315 512750 2500000 04:38.22 2418832


Subquery statistics:
--------------------

Table map :
----------------------------
Internal name Table name
----------------------------
t1 b

type table rows_prod est_rows rows_scan time est_cost
-------------------------------------------------------------------
scan t1 806330 1 821121 04:30.70 5

type rows_prod est_rows rows_cons time
-------------------------------------------------
group 806330 1 806330 04:31.89

castelo@primary:informix->
Alguns factos sobre esta execução:
  • Demorou cerca de 5m
  • Fez um scan completo à tabela e usou o índice mais complexo para resolver a sub-query (este fornece acesso fáacil ao MAX(customer_seq_id) por customer_num / customer_plan)
  • Passou a maioria do tempo à espera de I/O
E agora a segunda query:
castelo@primary:informix-> onmode -ky;oninit
castelo@primary:informix-> /usr/informix/bin/ixprofiling -w -i stores test_query_2.sql

Database selected.

Engine statistics RESETed. Query results:
Query start time: 09:58:23.477004000

SET EXPLAIN FILE TO "query_option_2.txt";
Explain set.


SET EXPLAIN ON;
Explain set.


UNLOAD TO /dev/null
SELECT
*
FROM (
SELECT
customer_num, customer_plan, customer_seq_id, customer_tax_code,
RANK() OVER (PARTITION BY customer_num, customer_plan ORDER BY customer_seq_id DESC) myrank
FROM
customer_tax_code a
WHERE
a.customer_service = 1
)
WHERE myrank = 1;
798315 row(s) unloaded.

Query stop time: 09:59:59.676333000

Thread profiles (SID: 6)
LkReq LkWai DLks TOuts LgRec IsRd IsWrt IsRWr IsDel BfRd BfWrt LgUse LgMax SeqSc Srts DskSr SrtMx Sched CPU Time Name
----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----------- ------------
250e4 0 0 0 0 19698 102e4 0 0 206e3 3997 0 0 3 1 1 18944 108e3 9.918404867 sqlexec
LkWs IOWs nIOW IdxBR Name
------------ ------------ ------------ ------------ -----------------------------------
0.0 74.245603123 3087 0 sqlexec


Session wait statistics:
Thread name Condition Num. waits Cum. time Max wait
----------------------------------- ----------------------------------- ------------ ------------ ------------
sqlexec mutex 6 76.625269575 19.0
sqlexec sort io 280 641848.77478 94357.0
sqlexec condition 4554 2177075.9388 21014.0
sqlexec mt yield 0 9538 2845631.3908 24543.0
sqlexec mt ready 108699 2931312.6256 24544.0
sqlexec buffer 191 5480268.6835 126467.0
sqlexec running 17658 9915220.0648 47373.0
sqlexec aio 3088 74141204.005 451378.0
sqlexec Total time 98132638.109

Partitions profiles (Database: stores)
LkReq LkWai DLks TOuts DskRd DskWr IsRd IsWrt IsRWr IsDel BfRd BfWrt SeqSc Object name
----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ------------------------------------------------------
[... SOME systables stats were removed for clarity ...]
250e4 0 0 0 26049 358 10154 0 0 0 179e3 3220 1 customer_tax_code



Database closed.


real 1m38.444s
user 0m0.140s
sys 0m0.750s
castelo@primary:informix->
QUERY: (OPTIMIZATION TIMESTAMP: 03-31-2014 09:58:23)
------
SELECT
*
FROM (
SELECT
customer_num, customer_plan, customer_seq_id, customer_tax_code,
RANK() OVER (PARTITION BY customer_num, customer_plan ORDER BY customer_seq_id DESC) myrank
FROM
customer_tax_code a
WHERE
a.customer_service = 1
)
WHERE myrank = 1

Estimated Cost: 42278
Estimated # of Rows Returned: 102550

1) (Temp Table For Collection Subquery): SEQUENTIAL SCAN

Filters: (Temp Table For Collection Subquery).myrank = 1


Query statistics:
-----------------

Table map :
----------------------------
Internal name Table name
----------------------------
t1 a
t2 (Temp Table For Collection Subquery)

type table rows_prod est_rows rows_scan time est_cost
-------------------------------------------------------------------
scan t1 1025501 1025501 2500000 00:07.19 101043

type rows_sort est_rows rows_cons time
-------------------------------------------------
sort 1025501 0 1025501 01:27.73

type it_count time
----------------------------
olap 1025501 01:31.31

type table rows_prod est_rows rows_scan time est_cost
-------------------------------------------------------------------
scan t2 798315 102550 1025501 00:00.47 42278

castelo@primary:informix->
Factos sobre esta execução:
  1. A query demorou cerca de 1m40s
  2. Fez o mesmo scan completo que a anterior
  3. Não necessitou de qualquer I/O pesado sobre nenhum dos índices
  4. Desperdicou pouco mais que 1m em esperas pelo I/O (versus cerca de 4m na anterior)
Conclusão
A tabela seguinte contém o sumário das diferenças entre ambas as queries:


Query 1 (sub-query)Query 2 (OLAP)Tempo de exeução4m49.367s1m38.444sThread ISAM reads831000,00019698,000Thread ISAM writes0,0001020000,000Thread buffer reads4930000,000206000,000Thread buffer writes0,0003997,000Thread SortMax0,00018944,000Thread schedules86801,000108000,000Thread CPU time26,7189,918Table ISAM reads17641,00010154,000Table buffer reads909000,000179000,000Table buffer writes0,0003220,000Table disk reads26049,00026049,000Table disk writes0,000358,000Index ISAM reads1630000,0000,000Index buffer reads4130000,0000,000Index disk reads30852,0000,000I/O waits16543,0003087,000I/O wait time251,79074,245
Como pode verificar por si próprio, a segunda query tem valores muito melhores no que é comum. Necessita de algum trabalho extra para ordenar e escrever a estrutura temporária. Mas mesmo assim, parece ter um impacto muito menor. Há ainda um outro aspecto que torna a primeira opção muito melhor com estes dados de teste que na situação real. Este aspecto por si só mereceria um artigo, mas de momento vou apenas referir que a forma como os dados foram criados, faz com que a tabela fique relativamente ordenada por customer_num/customer_plan. Este aspecto não é de todo irrelevante na situação real por dois motivos:
  1. No caso real o índice que existia tinha apenas customer_num/customer_plan e não tinha como terceiro campo o customer_seq_id que neste caso permite obter o MAX() apenas com o acesso ao índice
  2. Quando acedemos ao índice para resolver a sub-query, os rowids que obtemos tendem a estar próximos. Em muitos casos um único acesso a disco chegará para obter todas as linhas que validem a condição. Na situação real, obter os registos para o mesmo customer_num/customer_plan necessitaria de mais acessos a disco, e tipicamente mais aleatórios ou espalhados que neste caso. Ainda por cima, como estamos a fazer um scan completo para a query de fora, quando tentamos aceder por rowid aos registos com o mesmo customer_num/customer_plan, a probabilidade de esses dados já terem sido lidos pelo scan é grande. Se a tabela não estivesse "ordenada", isto não aconteceria e o impacto no I/O seria sensivelmente maior.
Como nota final, não consigo entender porque não podemos usar alias de colunas na cláusula HAVING. Se existe alguma razão que o justifique eu não a conheço. E não tenho a certeza se não seria ainda maior optimização se conseguíssemos sinalizar que apenas queremos um registo por grupo. Por esta razão inseri um novo pedido de melhoria (RFE) no site público. O URL para este pedido é: http://www.ibm.com/developerworks/rfe/execute?use_case=viewRfe&CR_ID=46670





AWK script used to generate the test data:

castelo@primary:informix-> cat generate_data.awk

function myrand(i)
{
return int(rand() * i) + 1;
}
BEGIN {

srand();

my_previous_customer=5000000;
my_previous_plan="MULTI";
my_previous_service=1;

list_tax_size=6;
list_tax_code[1]=15;
list_tax_code[2]=25;
list_tax_code[3]=35;
list_tax_code[4]=45;
list_tax_code[5]=55;
list_tax_code[6]=65;

list_plans_size=4;
list_plans[1]="MULTI";
list_plans[2]="SOLO";
list_plans[3]="GROUP";
list_plans[4]="KIDS";

client_with_service_1_percent=0.41;
same_client_plan_percent=0.5;
customer_num_repetition_percent=0.33
}


{
for(a=1;a<=$1;a++)
{
r=myrand(100);

if ( r <= customer_num_repetition_percent * 100 )
{
# % will repeat previouc customer_num
client_num = my_previous_customer;
client_service = my_previous_service;
r=myrand(100);
if ( r <= same_client_plan_percent * 100 )
{
client_plan = my_previous_plan;
r=myrand(list_tax_size);
client_tax_code=list_tax_code[r]
}
else
{
r=myrand(list_plans_size);
client_plan=list_plans[r];
r=myrand(list_tax_size);
client_tax_code=list_tax_code[r]
}
}
else
{
# random customer_num
client_num = myrand(10000000);
r=myrand(list_plans_size);
client_plan=list_plans[r];
r=myrand(100);
if ( r <= client_with_service_1_percent * 100 )
client_service=1;
else
client_service=r;
r=myrand(list_tax_size);
client_tax_code=list_tax_code[r]
}

my_previous_customer=client_num;
my_previous_plan=client_plan;
my_previous_service=client_service;
printf("%s|%s|%s|%s|%s\n",client_num,client_plan,client_service,0,client_tax_code);
}
}
castelo@primary:informix->

Categorias: Blogs em Português

New site for French speaking users

Informix-Techonology - dom, 30/03/2014 - 21:59

This article is written only in English (original version here)

Just a quick note to echo the creation of another Informix related site. This time is for all French speaking users. The site URL is http://informix.fr and the author is a well known member of the Informix community, Jean Georges Perrin who among many other things worked for Four J's (makers or Dynamic 4GL and Genero) and have been working in the IUUG's board of directors.

The site contains a forum (acutally two, one for technical questions and another for other discussions) and as far as my very limited French allows me to understand, it will count on some very experienced Informix people from France, like Eric Vercelletto, Laurent Revel and Olivier Bourdin (L3 Informix support).

If you speak/read French keep an eye on it, as I'm sure the people involved are able to guarantee high standards in the discussions and for sure interesting topics.
For better understanding the importance this can have, I remind you that there are many millions of French speaking people in places like France (who would say?!), Belgium, Switzerland, Monaco, Africa, Southeast Asia, some Pacific Islands and Canada. And of course there are many more places with people who can understand and express themselves in French (not my case). So the potential reach of this is huge!
France is also one of the places in Europe with L2 and L3 Informix support units. So there is a lot of Informix knowledge and talent there.

Categorias: Blogs em Português

Internet of Things - Série de videos relacionados ao IBM Informix

InformixBR - qui, 27/03/2014 - 09:20

Mais uma novidade da IBM para clientes, parceiros e integradores.

A IBM desenvolveu videos disponibilizados no Youtube para divulgar e facilitar o entendimento do contexto denominado "Internet of Things", ou seja, como lidar com todo este conteúdo gerado praticamente 24h por dia por celulares, computadores, dispositivos móveis dos mais variados, sensores, e por ai vai.

Saiba como otimizar e gerar valor com as soluções do IBM Informix para Internet of Things!


Introdução - Internet das Coisas
Uma introdução simples e fácil para entender tudo sobre a Internet das coisas.

Internet das coisas com a IBM Informix
Este vídeo descreve porque o IBM Informix é a chave para a Internet das Coisas

Assista os videos no Youtube


InformixBR

Categorias: Blogs em Português

Session limit locks / limite de locks por sessão

Informix-Techonology - qua, 26/03/2014 - 20:53

This article is written in English and Portuguese (original version here)
Este artigo está escrito em Inglês e Português (versão original aqui)


English version:

In a very recent article I was complaining about the fact that we have some non documented features, and that from time to time those "leak" into the community. It just happened again, and this time it was the exact example I mentioned in that article. As I mentioned before, this one was already mentioned in a 2011 presentation in IOD. Now it happened in the IIUG mailing list (more precisely in the mailing list that acts as a gateway to the newsgroup comp.databases.informix), but we can also find it in a similar presentation made in IIUG 2011 conference which is available to IIUG members in their site.

I'm talking about an ONCONFIG parameter called SESSION_LIMIT_LOCKS. And yes, the name is self explanatory... It defines the maximum number of locks a session can use. Using it can be the best way to avoid a misbehaved session (or user error) to have impact on other sessions or the whole system. Since version 10 I believe (or maybe 9.4) we are able to extend the lock table. That would be a great idea but in fact it never provided the desired result. The problem was not that it doesn't work, but usually what happens is that a user does a mistake like loading millions of records into a table without locking the table, or they forget some condition in a WHERE clause of an UPDATE or DELETE instruction. So, it means that it usually won't stop after a few hundred or thousand more locks. It takes several lock table extensions, and this may consume a lot of memory. So usually the end result is one of these:

  1. The user session ends up in a long transaction (very large - exceeding LTXHWM) with a very slow rollback
  2. The system consumes a lot of memory with the abnormal lock table expansion, and the engine may end up hitting the SHMTOTAL memory limit, or overloading the machine with memory usage and consequently with swapping
Either case, it's not good.
I know about this functionality since 2011 (11.70 but I'm not sure about the fixpack) but I was under the impression that it was not fully functional. I did some tests (showed below) on 12.10.xC3 and I couldn't find any issue with it. But please consider that if it's undocumented, you won't be able to complain if it fails... In fact, I present here the tests for version 12.10.xC3. Previous versions may have different (wrong) behavior. Use it at your own risk. You will not get support!

So, let's try it:
  • I have an instance with 20000 LOCKS:

    castelo@primary:informix-> onstat -c | grep "^LOCKS "
    LOCKS 20000
    castelo@primary:informix->

  • I create a very simple test case that will consume 600 locks:

    castelo@primary:informix-> cat test_locks.sql
    DROP TABLE IF EXISTS test_locks;

    CREATE TABLE test_locks
    (
            col1 INTEGER
    ) LOCK MODE ROW;

    INSERT INTO test_locks
    SELECT LEVEL FROM sysmaster:sysdual CONNECT BY lEVEL < 601;
    castelo@primary:informix->

To start,  let's see how the engine is setup by default:

castelo@primary:informix-> echo "SELECT * FROM syscfgtab WHERE cf_name = 'SESSION_LIMIT_LOCKS'" | dbaccess sysmaster

Database selected.




cf_id 88
cf_name SESSION_LIMIT_LOCKS
cf_flags 36928
cf_original
cf_effective 2147483647
cf_default 2147483647

1 row(s) retrieved.



Database closed.

castelo@primary:informix->
 So apparently by default it comes with (2^32) - 1 locks per session (or unlimited).
From several sources (IUG mailing list and IIUG 2011 conference presentation) we can assume this can be setup as an ONCONFIG parameter and a session variable. So let's start by trying to change the $ONCONFIG parameter:

castelo@primary:informix-> onmode -wm SESSION_LIMIT_LOCKS=100
SESSION_LIMIT_LOCKS is already set to 2147483647.
castelo@primary:informix->


Ops.... strange message... I tried other values and found the minimum value accepted seems to be 500:


castelo@primary:informix-> onmode -wm SESSION_LIMIT_LOCKS=500
Value of SESSION_LIMIT_LOCKS has been changed to 500.
castelo@primary:informix->

Let's verify:

castelo@primary:informix-> echo "SELECT * FROM syscfgtab WHERE cf_name = 'SESSION_LIMIT_LOCKS'" | dbaccess sysmaster

Database selected.




cf_id 88
cf_name SESSION_LIMIT_LOCKS
cf_flags 36928
cf_original
cf_effective 500
cf_default 2147483647

1 row(s) retrieved.



Database closed.

castelo@primary:informix->

Now we can try the test case and see what happens:

castelo@primary:informix-> dbaccess stores test_locks.sql

Database selected.


Table dropped.


Table created.


271: Could not insert new row into the table.

134: ISAM error: no more locks
Error in line 9
Near character position 56

Database closed.

castelo@primary:informix->

Great! If we limit to 500 locks, we cannot consume 600. That's a dream come true!
Furthermore, we can see it in online.log:

castelo@primary:informix-> onstat -m

IBM Informix Dynamic Server Version 12.10.FC3 -- On-Line -- Up 00:11:59 -- 287724 Kbytes

Message Log File: /usr/informix/logs/castelo.log

[...]

14:16:29 Maximum server connections 1
14:16:29 Checkpoint Statistics - Avg. Txn Block Time 0.000, # Txns blocked 0, Plog used 7, Llog used 2

14:16:34 Value of SESSION_LIMIT_LOCKS has been changed to 500.
14:16:39 Session SID=42 User UID=1002 NAME=informix PID=27743 has exceeded the session limit of 500 locks.

castelo@primary:informix->

Great! What else could we ask for?
Continuing with the tests... In the presentation it's also suggested that we can SET ENVIRONMENT... So:

castelo@primary:informix-> dbaccess stores <<EOF
> SET ENVIRONMENT SESSION_LIMIT_LOCKS "1000";
> EOF

Database selected.


19840: Invalid session environment variable.
Error in line 1
Near character position 41


Database closed.

castelo@primary:informix->

Ouch... it doesn't recognize the variable name... Can it be one of the reasons why it's not documented? Could be... but if we think about it, the latest session environment variables all start with IFX_ prefix and then have the $ONCONFIG parameter. So I tried with:

castelo@primary:informix-> dbaccess stores <<EOF
> SET ENVIRONMENT IFX_SESSION_LIMIT_LOCKS "1000";
> EOF

Database selected.


Environment set.



Database closed.

castelo@primary:informix->

Good! So if I add this to the test case:

castelo@primary:informix-> cat test_locks_1000.sql
DROP TABLE IF EXISTS test_locks;

CREATE TABLE test_locks
(
col1 INTEGER
) LOCK MODE ROW;

SET ENVIRONMENT IFX_SESSION_LIMIT_LOCKS "1000";
INSERT INTO test_locks
SELECT LEVEL FROM sysmaster:sysdual CONNECT BY lEVEL < 601;
castelo@primary:informix-> dbaccess stores test_locks_1000.sql

Database selected.


Table dropped.


Table created.


Environment set.


600 row(s) inserted.


Database closed.

castelo@primary:informix->


So, this does not means it works. But the above tests had the expected result. This is the best example of the situation I described in the previous article. Resources were used to implement this and it's not currently officially available to customers, although it was previously made public at least in three situations. Hopefully this will be documented soon. I feel this is one of the most wanted features in the customer's environment. Again, this would be a nice topic to be raised at IIUG conference in Miami


Versão Portuguesa:

Num artigo muito recente queixava-me do facto de termos funcionalidades não documentadas, e que ocasionalmente informação sobre essas funcionalidades "transparecia" para a comunidade. Ora isso acabou de acontecer, e desta feita exatamente com um dos exemplos que referia no artigo. Como escrevi na altura, isto já tinha sido apresentado numa conferência IOD de 2011. Desta vez foi referido na lista de correio do IIUG (mais precisamente na lista que serve de gateway com o newsgroup comp.databases.informix), mas também é possível encontrar uma referência ao mesmo tema numa apresentação da conferência do IIUG de 2011, estando essa apresentação disponível no site do IIUG na sua área reservada a membros.

Estou a falar de um parâmetro do ONCONFIG chamado SESSION_LIMIT_LOCKS. E sim, o seu nome diz tudo... define um máximo de locks que cada sessão pode usar. Usá-lo poderá ser a melhor forma de evitar que uma sessão "mal comportada" (ou um erro de utilização) tenha impacto nas outras sessões ou mesmo no sistema em geral.
Desde a versão 10 segundo creio (ou será 9.40?) que podemos expandir a tabela de locks. Isso seria uma excelente ideia, mas na verdade julgo que nunca teve o resultado planeado. O problema não está no facto de isso não funcionar, mas antes porque habitualmente o que acontece é alguém cometer um erro como carregar milhões de registos numa tabela sem a bloquear, ou esquecer uma condição numa cláusula WHERE de um UPDATE ou DELETE. Portanto, na prática o erro deixa de abortar por falta de locks (o Informix vai expandindo a tabela) ao fim de umas centenas ou milhares de registos. O resultado passa então a ser um destes:
  1. A sessão do utilizador acaba por gerar uma transação longa (muito grande - excedendo o LTXHWM) e força um rollback geralmente demorado
  2. O sistema consome muita memória devido às expansões anormais da tabela de locks, e isso pode levar o motor a bater no limite definido pelo SHMTOTAL, ou acaba por sobrecarregar a memória da máquina e eventualmente força o sistema a entrar em swapping
Em qualquer dos casos, o resultado não é bom!
Eu tenho conhecimento desta funcionalidade sensivelmente desde 2011, salvo erro num fixpack da 11.7, mas tinha a sensação que não estava funcional. No entanto fiz alguns testes (ver abaixo) na versão 12.10.xC3 e não consegui encontrar qualquer problema. Mas tenha em conta que algo não documentado é algo sobre o qual não se poderá queixar... Na verdade, apresentarei testes com a versão 12.10.xC3. Outras versões poderão ter comportamentos diferentes (errados). Use por sua conta e risco. Não terá suporte!

Vamos tentar então:
  • Tenho uma instância com 20000 LOCKS:

    castelo@primary:informix-> onstat -c | grep "^LOCKS "
    LOCKS 20000
    castelo@primary:informix->

  • Criei um caso de teste muito simples que ao ser executado vai consumir cerca de 600 locks:

    castelo@primary:informix-> cat test_locks.sql
    DROP TABLE IF EXISTS test_locks;

    CREATE TABLE test_locks
    (
            col1 INTEGER
    ) LOCK MODE ROW;

    INSERT INTO test_locks
    SELECT LEVEL FROM sysmaster:sysdual CONNECT BY lEVEL < 601;
    castelo@primary:informix->

Vamos começar por ver qual é a configuração do motor por omissão:

castelo@primary:informix-> echo "SELECT * FROM syscfgtab WHERE cf_name = 'SESSION_LIMIT_LOCKS'" | dbaccess sysmaster

Database selected.




cf_id 88
cf_name SESSION_LIMIT_LOCKS
cf_flags 36928
cf_original
cf_effective 2147483647
cf_default 2147483647

1 row(s) retrieved.



Database closed.

castelo@primary:informix->
 
Aparentemente, a pré-configuração são (2^32) - 1 locks por sessão (ou ilimitado).
Através de várias fontes (lista de correio do IIUG e apresentação feita na conferência do IIUG de 2011), podemos assumir que a´configuração pode ser feita por parâmetro do ONCONFIG e variável de sessão. Vamos então começar por mudar o parâmetro do ONCONFIG:

castelo@primary:informix-> onmode -wm SESSION_LIMIT_LOCKS=100
SESSION_LIMIT_LOCKS is already set to 2147483647.
castelo@primary:informix->

Ops.... mensagem estranha... Mas eu tentei outros valores e aparentemente o mínimo que podemos definir são 500 locks:

castelo@primary:informix-> onmode -wm SESSION_LIMIT_LOCKS=500
Value of SESSION_LIMIT_LOCKS has been changed to 500.
castelo@primary:informix->

Vamos verificar:

castelo@primary:informix-> echo "SELECT * FROM syscfgtab WHERE cf_name = 'SESSION_LIMIT_LOCKS'" | dbaccess sysmaster

Database selected.




cf_id 88
cf_name SESSION_LIMIT_LOCKS
cf_flags 36928
cf_original
cf_effective 500
cf_default 2147483647

1 row(s) retrieved.



Database closed.

castelo@primary:informix->

Parece bem. Agora podemos tentar o caso de teste que deveria exceder os locks permitidos e ver o que acontece:

castelo@primary:informix-> dbaccess stores test_locks.sql

Database selected.


Table dropped.


Table created.


271: Could not insert new row into the table.

134: ISAM error: no more locks
Error in line 9
Near character position 56

Database closed.

castelo@primary:informix->

Excelente! Se definimos 500, não podemos consumir 600. Parece um sonho tornado realidade!
Mas mais ainda, podemos ver isto no online.log:

castelo@primary:informix-> onstat -m

IBM Informix Dynamic Server Version 12.10.FC3 -- On-Line -- Up 00:11:59 -- 287724 Kbytes

Message Log File: /usr/informix/logs/castelo.log

[...]

14:16:29 Maximum server connections 1
14:16:29 Checkpoint Statistics - Avg. Txn Block Time 0.000, # Txns blocked 0, Plog used 7, Llog used 2

14:16:34 Value of SESSION_LIMIT_LOCKS has been changed to 500.
14:16:39 Session SID=42 User UID=1002 NAME=informix PID=27743 has exceeded the session limit of 500 locks.

castelo@primary:informix->

Ótimo! O que poderíamos pedir mais?!
Continuando com os testes... Na referida apresentação é sugerido que podemos usar a instrução SET ENVIRONMENT... Portanto:

castelo@primary:informix-> dbaccess stores <<EOF
> SET ENVIRONMENT SESSION_LIMIT_LOCKS "1000";
> EOF

Database selected.


19840: Invalid session environment variable.
Error in line 1
Near character position 41


Database closed.

castelo@primary:informix->

Ouch... Não reconhece o nome da variável... Poderá ser uma das razões porque ainda não está documentado? Talvez... mas se pensarmos um pouco, as últimas variáveis de sessão que têm sido introduzidas, todas começam com o prefixo "IFX_" e depois têm o nome do parâmetro equivalente no ONCONFIG. Assim sendo, tentei isto:

castelo@primary:informix-> dbaccess stores <<EOF
> SET ENVIRONMENT IFX_SESSION_LIMIT_LOCKS "1000";
> EOF

Database selected.


Environment set.

Database closed.

castelo@primary:informix->

Boa! Depois adicionei isto ao caso de teste:

castelo@primary:informix-> cat test_locks_1000.sql
DROP TABLE IF EXISTS test_locks;

CREATE TABLE test_locks
(
col1 INTEGER
) LOCK MODE ROW;

SET ENVIRONMENT IFX_SESSION_LIMIT_LOCKS "1000";
INSERT INTO test_locks
SELECT LEVEL FROM sysmaster:sysdual CONNECT BY lEVEL < 601;
castelo@primary:informix-> dbaccess stores test_locks_1000.sql

Database selected.


Table dropped.


Table created.


Environment set.


600 row(s) inserted.


Database closed.

castelo@primary:informix->


Bom, nada disto garante que funcione. Mas os testes acima tiveram o resultado esperado. Isto é o melhor exemplo da situação que descrevi no artigo já referido. Foram consumidos recursos para implementar isto, mas não está oficialmente disponível para os clientes, embora já tenho sido referido publicamente três vezes antes. Esperemos que isto seja documentado em breve. No meu entender esta é uma das funcionalidades mais desejadas pelos clientes. Refiro novamente que este seria um bom tópico de discussão na conferência de  utilizadores que se avizinha em Miami

Categorias: Blogs em Português

Where is Informix? / Onde anda o Informix?

Informix-Techonology - seg, 24/03/2014 - 20:22

This article is written in English and Portuguese (original version here)
Este artigo está escrito em Inglês e Português (versão original aqui)


English version:

This is a very simple post just to echo an article from a friend and former colleague, Eric Vercelletto. I can say a couple of things about Eric. To start, he has a long history with Informix, has a lot of experience in IT in general, I learned quite a lot with him, he's fun, he was the original author of a script that I still maintain (to select among several Informix environments), although the site where it should be available is always down due to lack of time, and finally I can never write his last name without a typo! :)

All this to say it's a pleasure to see him active in the community. He recently created the website http://www.informix-swat.com to join Informix specialist and companies looking for them, has just recently introduced an RFE (Request For Enhancement) for an interesting subject (you got my comment on that one) and last but not least wrote a very interesting paper about the topic: Where is Informix?
In this document Eric talks about history and I do agree with most of it. I do have some remarks though:

  • The version launch dates are correct and show something that is impressive for a product that our competitors pretend that is dead: Since 2001 (13 years ago), we've released a major release with at most around two years interval. And during the life cycle of a version we keep a steady rate of fixpacks bringing updates, fixes and since a few years ago continuous innovation and new features.
    And as I wrote recently in a discussion on the IIUG mailing list, we're currently providing an N-2 support policy, meaning we support the current version (12.10 or "N"), the previous one (11.70 or "N-1") and the one before that (11.50 or "N-2"). I don't like the idea, but I'm sure many customers do
  • Eric says 11.50 was "small enhancements and adjustments". I wouldn't say so... It contained:
    • UPDATES on secondaries
    • SSL
    • Optimistic locking through the hidden version columns (introduced to facilitate updates on secondaries)
    • Dynamic SQL in SPL procedures
    • Data compression (in later fixpacks)
  • Eric says IBM had decided to incorporate Informix features in DB2. I think only a bunch of people really know what were the initial plans, the present plans, and the future plans. And as with all long term planning, it changes. The facts show that yes, there has been some technology interchange and I wouldn't expect anything different:
    • DB2 inherited the basic HDR concept (called HADR on DB2)
    • Informix inherited some UNICODE support
    • Informix got DRDA
    • Informix got compression (appeared first on DB2)
    • Informix and DB2 inherited in-memory technology from the "blink project" (implemented differently and first in Informix)
    • I believe Informix inherited encryption from DB2 (the same functions exist across DB2 product lines/platforms)
    • JSON exists on both (not necessarily with the same functionality)
  •  Eric says "It was also rumored that at this time some IBM sales persons would not hesitate to sell the ‘Red DBMS’ to Informix customers"
    Well... The magic of a rumor is that it can be rebated. But let's analyze this: IBM is a giant with several areas (software, hardware, business services, technical services, financing etc.). These areas try to maximize synergies to supply end to end solutions. But they also act "solo" on the market. The majority of the Unix/Linux market belongs to Oracle. Are you surprised that my hardware colleagues want to sell Power systems (AIX) to those customers? I'm not! Business services try to win deals in Oracle or MS SQL Server shops. Are you surprised they won't refuse to work with those products or even include them in their solutions if the customer has a preference?! I'm not.
    A completely different thing would be to imagine that a software sales person would "sell" Oracle. For start, it's impossible. And more important: A sales person follows the money. And they would get commissions on those impossible sales :)
But again, the paper is very interesting to read. And shows a clear image: The investment in the product is there. The fact that it's less known than it should, the fact that customers are loyal etc.
Well done!


Versão Portuguesa:

Este é um artigo muito simples, apenas para fazer eco de um artigo de um amigo e antigo colega, Eric Vercelletto. Para começar o Eric tem um longo historial com Informix, uma grande experiência em TI, aprendi bastante com ele, foi o autor original de um script que ainda mantenho (para selecionar entre ambientes Informix) apesar de o site onde deveria estar passar mais tempo em baixo que ligado, e finalmente nunca consigo escrever o seu apelido sem me enganar :)

Tudo isto para dizer que é um prazer vê-lo ativo na comunidade. Ainda recentemente criou um site (http://www.informix-swat.com) para juntar os especialistas Informix e empresas que andem à sua procura. registou um RFE (Request For Enhancement) sobre um tema interessante, e por último publicou um artigo sobre o tema: Onde está o Informix? (Where is Informix?)
No mesmo, o Eric fala sobre a história do Informix e concordo com a maioria do que escreve. Mas tenho alguns comentários:
  • As datas de lançamento das versões estão corretas e mostram algo que é impressionante sobre um produto que a concorrência faz de conta que morreu: Desde 2001 (há 13 anos atrás) temos lançado uma versão major com no máximo cerca de dois anos de intervalo. E durante o ciclo de vida de cada versão mantemos um ritmo previsível de fixpacks que trazem updates, correções  e de há uns anos para cá inovação continuada e novas funcionalidades.
    E como escrevi recentemente numa discussão na lista de correio do IIUG, atualmente está em vigor uma política de suporte "N-2" para o Informix, o que significa que suportamos a versão mais atual (12.10 ou "N"), a anterior (11.70 ou "N-1"), e a que precedeu esta (11.50 ou "N-2"). Pessoalmente não gosto da ideia, mas tenho a certeza que muitos clientes gostam
  • O Eric diz que a 11.50 foi "small enhancements and adjustments" (pequenas melhorias e ajustes). Não diria tanto.... Continha:
    • UPDATES nos secundários
    • SSL
    • Optimistic locking pelo uso de colunas de versão escondidas (introduzido para facilitar os updates nos secundários)
    • SQL dinâmico nos procedimentos SPL
    • Compressão de dados (em fixpacks posteriores)
  • O Eric diz que a IBM decidiu incorporar funcionalidades do Informix no DB2. Penso que apenas um punhado de pessoas sabiam realmente quais eram os planos iniciais, quais são os atuais ou quais serão os futuros. É que como qualquer planeamento de médio/longo prazo, as situações mudam. Os factos mostra efetivamente que houve transferência de tecnologia e não seria de esperar algo diferente:
    • O DB2 herdou a base do HDR (chamado HADR no DB2)
    • O Informix herdou o suporte a UNICODE
    • O Informix recebeu o  DRDA
    • O Informix recebeu a compressão (apareceu primeiro no DB2)
    • O Informix e o  DB2 herdaram a tecnologia de base de dados em memória do "projecto blink" (implementado primeiro no Informix e agora de forma diferente no DB2
    • Julgo que o Informix herdou a encriptação (colunas) do DB2 (as funções existem em várias plataformas do DB2)
    • O JSON existe em ambos (não necessariamente com as mesmas funcionalidades)
  • O Eric diz "It was also rumored that at this time some IBM sales persons would not hesitate to sell the ‘Red DBMS’ to Informix customers" ("existe também o rumor que os vendedores da IBM não hesitavam em vender a RDBMS vermelha a clientes Informix")
    Bom... a magia dos rumores é que não vale a pena rebatê-los. Mas vamos analisar isto: A IBM é um gigante com muitas áreas (software, hardware, serviços de negócio, serviços técnicos, financiamento etc.). Estas áreas tentam maximizar sinergias para fornecerem soluções completas. Mas também atuam "a solo" no mercado. A maioria do mercado Unix/Linux pertence à Oracle. Surpreende-o que os meus colegas de hardware queiram vender sistemas Power a esses clientes? A mim não! Os serviços de negócio ganham negócios em clientes Oracle e MS SQL Server. Surpreende-o que não se recusem a trabalhar com esses produtos ou mesmo incluí-los nas suas soluções se o cliente tiver uma preferência? A mim não!
    Algo completamente diferente seria imaginar que um vendedor de software tentasse vender Oracle. Para começar seria impossível. E mais importante: Um vendedor segue o dinheiro. E neste caso não receberiam nada por essas vendas impossíveis
Mas mais uma vez, o documento está interessante. E mostra uma imagem clara: O investimento no produto, o facto de que é menos conhecido que o que merecia, o facto de que os clientes são fiéis etc.
Muito bem!

Categorias: Blogs em Português

Explain plans: for the last time / Planos de execução: pela última vez

Informix-Techonology - ter, 18/03/2014 - 20:39

This article is written in English and Portuguese (original version here)
Este artigo está escrito em Inglês e Português (versão original aqui)

English version:

If there is a topic that have always caught my attention it's the ability (or lack of) to capture a query plan in a client tool. This has been one of the most dirty Informix "secrets". This should be an essential feature of any RDBMs, and Informix provided the query plan, but in a very akward manner. A file was written on the database server (or on a filesystem mounted on the database server). This served us well enough when programmers used to work on the same machine as the database server, but those times are gone (a long time ago). I dedicated some time to a rather complex way of solving this after IBM introduced a function called EXPLAIN_SQL() that would return an XML file containing the query plan. This was the topic for my presentation on IIUG conference in 2010, but it was no more than a proof of concept. In other words it was not useable.
More recently I implemented a much simpler way to have the plan in any tool and documented it here in the blog. This works, it's simple and has little to no drawbacks. But it's still an hack...

Now, with some 20 years of delay, an article tells us that IBM has finally implemented this. There is a new function called ifx_explain() that accepts the query for which we want the query plan and returns the plan in text format. There is also an equivalent function called bson_explain() that returns the query plan in BSON format which can be casted to JSON (::JSON)

Please note that this is not documented. This new website (meanwhile it was added to the link list) is very recent and seems to be a great source of information. Some tweets mentioned it as belonging to John Miller who is one of the historic guys behind Informix (he was involved in all things related to backups, he's the "father" of Open Admin Tool, has several articles which even today are the references about update statistics, is deeply involved in JSON functionality etc.).

As a final note, I'd like to speak about something that is known to happen and this is just a clear example of that: we have implemented features in the engine which are not documented. The reason for that should be that they haven't passed (does not mean they failed or would fail) through the QA tests considered necessary to allow their general use. Which is understandable. But my point is that some of these were implemented too long ago. Time and resources were employed in creating them, and after too much time customers still haven't seen the benefits of that work. From my humble and personal (I must underline the "personal" for obvious reasons) perspective, there are some major issues with these situations:

  1. The cost (time and resources translate directly into money) it took to implement them are a waste until the day customers can use them and we can speak about them
  2. Some features (specifically SQL compatibility functions) may be used by customers without them knowing they're not documented. As an hypothetical example consider the new QUARTER() function announced in 12.10.xC3. A customer that is used to use that function in other RDBMs may just write it in SQL queries. If the server accepts it, he won't noticed if by any chance it was not (yet) documented. And if it's buggy, nasty things could happen, because in theory the customer was using a "non-existent" function, that was accepted by the server.
  3. Occasionally some of these functionalities are "leaked". I won't forget that I saw an ONCONFIG parameter in one of the sessions of IOD conference a few years ago, that was a very necessary feature for customers. I mentioned that to at least one customer and later I noticed it was not documented. After internal questioning the official position was "it didn't go through proper QA". Well... a bit late. Neither the slide had information that was supposed to be undocumented nor did I check that it was not documented. I simply tested and it worked!
So, my position about this is simple: If it's supposed to be used, it must be documented. If it's not supposed to be used, the engine MUST not accept it.  And in some cases I feel we're needing just a little bit more work (for QA) so that we can document those features and take "profit" (meaning allowing customers to use them) from all the investment put into it's creation.
Maybe the users and my colleagues that will be joining the IIUG 2014 conference in Miami want to include this topic in their discussions?

Having said this, it's a great day, as we closed a sad story about Informix!
If you're using 12.10.xC2+ then use this new feature. If not, try my soluction referenced above.

Versão Portuguesa:

Se há um assunto que sempre me mereceu atenção é a capacidade (ou falta dela) de capturar um plano de execução de uma query e apresentá-lo  numa ferramenta cliente. Este tem sido um dos "segredos sujos" do Informix. Isto será uma funcionalidade essencial a qualquer sistema de bases de dados, e de facto o Informix sempre disponibilizou o plano de execução, mas de uma maneira muito arcaica. O mesmo é escrito num ficheiro localizado (ou pelo menos acessível) no servidor de base de dados. Isto servia-nos razoalvelmente bem quando os programadores costumavam trabalhar na mesma máquina onde corria a base de dados. Mas esses tempos já lá vão (há muito tempo...).
Dediquei algum tempo a uma solução complexa para resolver isto, quando a IBM introduziu uma função chamada EXPLAIN_SQL() que devolvia a representação do query plan em XML. Este foi inclusive o tópico da minha apresentação na conferência de utilizadores do IIUG em 2010. Mas nunca passou de uma prova de conceito, ou por outras palavras nunca foi algo utilizável.
Mais recentemente implementei de forma muito mais simples a obtenção do plano de execução nas ferramentas cliente e documentei-o aqui no blog. Esta forma funciona e tem poucos ou nenhumas desvantagens. Mas ainda assim é um truque...

Porém agora, com uns vinte anos de atraso, apareceu um artigo que nos diz que a IBM finalmente implementou isto. Existe uma nova função chamada ifx_explain() que aceita a query para a qual queremos obter o plano de execução e retorna o mesmo sob a forma de texto simples. Existe ainda uma função semelhante, chamada bson_explain() que retorna o plano como um objecto BSON que pode ser transformando em JSON (::JSON)

Tenha em consideração que isto não está documentado. Este novo website (entretanto adicionado à lista de links) é muito recente e parece ser uma excelente fonte de informação. Alguns tweets mencionam que pertencerá ou que foi criado pelo John Miller, que é nem mais nem menos que um dos "históricos" por detrás do Informix (esteve envolvido com tudo o que se relaciona com backups, é o "pai" do Open Admin Tool, tem vários artigos que ainda hoje são as referências sobre o UPDATE STATISTICS, está profundamente envolvido com as funcionalidades JSON etc...)

Como nota final, gostaria de falar sobre algo que se sabe acontecer e este caso é um exemplo claro disso mesmo: temos implementado funcionalidades no motor que não se encontram documentadas. A razão para tal deverá ser que as mesmas não passaram (não necessariamente que falharam ou falhassem) pelos devidos testes de qualidade (QA), de forma a estarem prontas para uso genérico nos clientes. E isto parece-me razoável. Mas o meu "problema" é que algumas delas já foram implementadas há demasiado tempo. Tempo e recursos foram empregues para as criar, e mesmo depois de muito tempo os clientes ainda não podem tirar proveito desse esforço. Da minha humilde e pessoal (e é necessário reforçar o "pessoal" por motivos óbvios) perspectiva há vários potencias problemas que derivam destas situações:
  1. Os custos (tempo e recursos traduzem-se directamente em dinheiro) que derivaram da implementação destas funcionalidades são um desperdício até ao dia em que os clientes as possam usar e que possamos falar delas
  2. Algumas funcionalidades (especificamente funções de compatibilidade SQL) podem ser usadas pelos clientes, sei que eles saibam que não são documentadas. Como exemplo hipotético, consideremos a nova função QUARTER() introduzida na 12.10.xC3. Um cliente que esteja habituado a escrever SQL com essa função noutra base de dados, pode perfeitamente escrevê-la em Informix. Se o servidor a aceitar, o cliente não se irá aperceber se a função está ou não (ainda) documentada. E se a mesma estiver ainda imperfeita ou instável, coisas imprevisíveis podem acontecer, simplesmente porque um cliente usou algo "que não existe" mas que o servidor aceitou
  3. Ocasionalmente, algumas destas funcionalidades "transparecem" para a comunidade. Não me vou esquecer de ter visto um parâmetro de $ONCONFIG numa sessão de uma conferência IOD há alguns anos atrás, que ativa uma funcionalidade bastante necessária aos clientes. Eu já a mencionei a pelo menos um cliente e só depois me apercebi que não estava documentada. Depois de indagar internamente entendi que a posição oficial era "não sofreu testes significativos de QA". Bom... um pouco tarde demais. Nem o diapositivo tinha informação de que não estava documentado, nem eu verifiquei isso. Apenas fiz alguns testes e funcionou!
Portanto, a minha posição sobre o tema é simples: Se é suposto ser usado tem de estar documentado. Se não é suposto ser usado o motor NÃO pode aceitar. E em alguns casos sinto que necessitamos um pedacinho mais de esforço (para QA), de forma a que possamos documentar estas funcionalidades e obter "lucro" (ou seja, permitir que os clientes as usem) de todo o investimento colocado na sua criação.
Talvez os utilizadores e colegas que vão estar presentes na conferência do IIUG 2014 em Miami queiram incluir este tópico nas suas discussões?

Posto isto, foi um grande dia, pois fechámos uma história triste do Informix.
Se usa uma versão 12.10.xC2+ use esta nova funcionalidade. Senão recorra à forma que documentei e que referi no início

Categorias: Blogs em Português

Informix 12.10.xC3

Informix-Techonology - seg, 17/03/2014 - 22:36

This article is written in English and Portuguese (original version here)
Este artigo está escrito em Inglês e Português (versão original aqui)

English version:

IBM has just released Informix 12.10.xC3 this last Friday (March 14). The packages were available on FixCentral since the beginning of the week, but the documentation was updated only on Friday. As usual for a few years each fixpack contains small improvements and a few gems. This one is no exception. Here is the list directly from the documentation with a few comments added:

  • Migration
    • Server changes
    • JSON compatibility pre- and post-migration requirements
    • New reversion requirements
      These are "just" some new parameters and things we should consider when upgrading or downgrading. I will refer to some of them later
  • Installation
    • Server configuration
      • Automatically configure the server during installation
        If we choose to configure a server during installation, resources are adjusted automatically and the JSON listener is started
  • Administration
    • Autonomics
      • Automatic resource tuning for performance
        Some of the new parameters mentioned above are relevant to this. As an example we can have a dynamic buffer pool, let the server create more logical logs, let it adjust the physical log size and reconfigure the CPU and AIO VPs. This is another great step in the autonomic chapter.
      • Automatic location and fragmentation
        Another major change. If you prefer (set AUTOLOCATE parameter) the new tables are automatically created in a set of dbspaces, and fragmented in round-robin. If the tables grow, new fragments are automatically created. No more "no more pages" errors. For the old timer DBA (like me) this sounds tricky... but times do change and we must keep up. You're not forced to use this, but it will probably fit some environments
  • Performance
    • Control the size of private memory caches
      In previous fixpacks the private memory caches used by each CPU VP changed to dynamic. Now you have the option to make them static
    • Virtual shared memory segment size doubling
      Like we do in the extents for the tables, each 16 memory segments that we allocate we double their size. The idea is to keep their number low. But again, for old timers, 16 is already a very large number. We usually try to keep them below 5 (?). But this also means we can have them lower initially because we know that if the server grows to much, it will adapt.
  • Connectivity
    • Retrying connections
      As we've seen in 11.70.xC8, we can now define INFORMIXCONTIME and INFORMIXCONRETRY as $ONCONFIG parameters and by using the SET ENVIRONMENT statement.
      This seems nice but to be honest I was not really getting it (I should have noticed it when I wrote about 11.70.xC8). My confusion is caused by the fact that I usually use these settings to avoid spending too much time trying to connect to a dead or unreachable server. But if we cannot reach the server how would having these settings on the server side help?!
      Because this is to be applied on distributed queries and for that it makes sense.
  • Application development
    • JSON compatibility
      • Use the MongoDB API to access relational data
        You can access relational tables with the MongoDB API methods
      • Improved JSON compatibility
        New MongoDB API methods are supported like findAndModify() and some authentication related operations
    • Foreign-key constraints
      • Temporarily prevent constraint validation
        As in 11.70.xC8 this introduces the ability to use NOVALIDATE on foreign-key constraints creation to speed up the process
      • Faster creation of foreign-key constraints
        As in 11.70.xC8, real creation of foreign keys can take better advantage of existing indexes
  • Compatibility
    • Find the quarter of the calendar year for dates
      Implementation of the QUARTER() SQL function. A request from the market for better integration with 3rd party BI tools
  • High-availability clusters and Enterprise Replication
    • Connection Manager
      • Improvements to Connection Manager
        Introduction of two new redirection policies: ROUNDROBIN and SECAPPLYBACKLOG
    • Monitoring
      • View log-staging information on RS secondary servers
        Get information about log staging information in RSS servers where the DELAY APPLY functionality is in use
    • Configuration
      • Easier configuration and cloning of a server for replication
        Easily configure and start Enterprise Replication
    • Sharding
      • Shard data across Enterprise Replication servers
        Shard is a term that refers to the distribution of a single object across a number of nodes. The purpose is a bit like "divide and conqueror". By distributing records, documents or rows we make them more manageable in each node and gain performance by having more hardware dealing with our data. Informix can now "shard" relational tables and collections (JSON) across instances in an Enterprise Replication domain
  • Spatial data
    • Enhancements for handling spatial data
      More spatial reference systems and the ability to calculate area and distance for data based on the round-Earth model.
      Informix spatial data types now conform to the OpenGIS Simple Features Specification for SQL Revision 1.1 and the ISO/IEC 13249-3 SQL/MM Part 3: Spatial. The Informix spatial solution is based on the ESRI SDE 10.2 Shape and PE libraries.
  • Time series data
    • Storage
      • Efficient storage for hertz and numeric time series data
        Timeseries can store a series of sub-second values in a packed element (up to 4KB)
    • Containers
      • Control the destroy behavior for rolling window containers
        Limit the number of windows of a rolling window container that can be destroyed in a single operation
      • Monitor groups of containers with wildcard characters
        Several monitorization functions now allow wildcards in the container name
  • Faster queries
    • Faster queries by running time series routines in parallel
      Functions that can be used in WHERE clause of a SELECT statement now can take advantage of PDQ PRIORITY (parallelism) if the tables are fragmented
    • Faster queries with IN conditions through virtual tables
      Optimization for queries with IN conditions
  • Warehousing
    • Additional types of data
      • Accelerate warehouse queries in-memory using data from multiple sources
        Synonyms and views can  now reference tables in different databases, tables in databases of the same Informix instance, or tables in a different Informix instance. This work for JSON data also

Beyond the very light review above, I'd like to highlight a few points:
  1. Dynamic buffer pool. Although currently it can "only" be set to self tune (it will grow within specified limits if the read cache hits are lower than a threshold we define), this will be an historical step. I remember that while explaining the dynamic parameters we were introducing, I sometimes mentioned that "some like BUFFERPOOL will possibly never be dynamic".... Well, I must rethink that statement. Although currently it's not dynamic, if it can automatically adjust the buffers, I imagine we'll be able to do the same in the future (by using onmode -wm/wf for example)
  2. You may noticed that some features introduced in xC3 of version 12.10 were already introduced in xC8 of version 11.70. This happens because some features are being developed across more than one code line, and they appear first in version N-1 or N depending on the release schedule and calendar. Nothing to do with marketing... If the code change is feasible in more than one version we give it to customers. We don't force them to upgrade to get any new stuff. How many vendors incorporate new features in previous product versions?!
  3.  Continuous improvements in many distinctive areas: Timeseries, JSON, IWA.




Versão Portuguesa:

A IBM acabou de lançar o fixpack 12.10.xC3 esta última sexta-feira (14 de Março). Os pacotes estavam disponíveis no FixCentral desde o inicio da semana, mas a documentação só foi atualizada na sexta-feira. Como é habitual desde há uns anos, cada fixpack contém algumas pequenas melhorias e algumas pérolas. Este não é exceção. Aqui está a lista diretamente da documentação com alguns comentários adicionados:
  • Migração
    • Mudanças no servidor
    • Compatibilidade com JSON: requisitos pré e pós-migração
    • Novos requisitos para regressões
      Existem alguns novos parâmetros e algumas ações a ter em conta quando se efetua um upgrade ou downgrade de versão. Irei mencionar alguns mais tarde
  • Instalação
    • Configuração do servidor
      • Configuração automática do servidor durante a instalação
        Se escolhermos configurar um servidor durante a instalação, os recursos são ajustados automaticamente e o serviço de JSON é iniciado
  • Administração
    • Autonomics
      • Ajuste de recursos automático para melhorar o desempenho
        Alguns dos parâmetros mencionados acima são relevantes para isto. Como exemplo podemos ter uma área de buffers dinâmica, deixar o servidor criar mais logical logs, alterar o physical log e reconfigurar os VPs do tipo CPU e AIO. È mais um passo significativo no capítulo da auto-gestão dos servidores
      • Fragmentação e alocação automática
        Mais uma mudança significativa. Se preferirmos (definindo o parâmetro AUTOLOCATE) as novas tabelas são espalhadas automaticamente por um conjunto de dbspaces e fragmentadas por round-robin. Se as tabelas crescerem, novos fragmentos serão automaticamente criados. Acabam-se os erros "no more pages". Para os DBAs "antigos" (como eu) isto parece suspeito... Mas os tempos mudam e temos de os acompanhar. Não somos obrigados a usar isto, mas sem dúvida que se ajusta a alguns ambientes.
  • Desempenho
    • Controlo sobre o tamanho das caches privadas
      Em fixpacks anteriores a memória privada alocada às caches de cada CPU VP passou a ser dinâmica. Agora existe a possibilidade de escolha e podemos defini-las com um tamanho estático também
    • Duplicação do tamanho dos segmentos virtuais
      Tal como fazemos nos extents das tabelas, a cada 16 segmentos que alocarmos o seu tamanho duplica. A ideia é manter o seu número "baixo". Mas mais uma vez, para os "antigos", 16 já é um valor demasiado alto. Normalmente tentamos manter o seu número abaixo de 5 (?). Mas apesar disso, isto significa que talvez possamos definir o seu tamanho muito mais pequeno ao início, pois sabemos que se o servidor crescer muito irá adaptar-se
  • Conectividade
    • Definição de tentativas de conexão
      Como vimos na 11.70.xC8, podemos agora definir as variáveis INFORMIXCONTIME e INFORMIXCONRETRY como parâmetros no $ONCONFIG e pela utilização da instrução SET ENVIRONMENT.
      Isto parece muito bem, mas para ser honesto, receio que não estava a compreender totalmente esta funcionalidade (e devia ter notado isto quando escrevi sobre a 11.70.xC8). A minha confusão foi causada pelo fato de que habitualmente uso estes parâmetros ou variáveis para evitar perder muito tempo a tentar ligações a um servidor que está em baixo ou não está acessível. Mas nesses casos, como é que ter estes parâmetros do lado do servidor ou numa sessão já estabelecida ajudariam?! Na verdade a ideia é aplicar isto a queries distribuídas e aí já faz todo o sentido
  • Desenvolvimento aplicacional
    • Compatibilidade com JSON
      • Usar a MongoDB API para aceder a dados relacionais
        Podemos aceder a tabelas relacionais (tradicionais) do Informix com os métodos da MongoDB API
      • Mais compatibilidade com  JSON
        Mais métodos da MongoDB API são agora suportados como o findAndModify() e alguns outros relacionados com autenticação
    • Chaves estrangeiras
      • Desabilitar temporariamente a validação das chaves estrangeiras
        Como na 11.70.xC8, isto introduz a possibilidade de usar a cláusula NOVALIDATE na criação de chaves estrangeiras
      • Criação mais rápida de chaves estrangeiras
        Tal como na 11.70.xC8, a criação de chaves estrangeiras (com validação) pode tirar mais proveito de índices já existentes
  • Compatibilidade
    • Obter o trimestre (quarter) de uma data
      Implementação da função SQL QUARTER(). Um pedido do mercado para integração com terceiros, fornecedores de ferramentas de BI
  • Clusters de alta-disponibilidade e Enterprise Replication
    • Connection Manager
      • Melhorias no Connection Manager
        Introdução de duas novas políticas de redirecionamento: ROUNDROBIN e SECAPPLUBACKLOG
    • Monitorização
      • Obter informação sobre log staging em servidores secundários remotos
        Em servidores onde o DELAY APPLY foi ativado é possível agora obter informação sobre os logs que vão sendo acumulados
    • Configuração
      • Facilidade na configuração de um servidor para Enterprise Replication
        A ferramenta de clonagem (ifxclone) facilita agora a ativação automática de Enterprise Replication
    • Sharding
      • Shard de dados entre servidores configurados em Enterprise Replication
        "Shard" é um termo que se refere à distribuição de um objeto pelos nós de um cluster de replicação. O objetivo é um pouco "dividir para reinar". Ao distribuir registos, documentos ou linhas torna-mo-los mais manejáveis em cada nó e ganhamos desempenho pois temos mais hardware para os gerir. O Informix pode agora fazer o shard de tabelas relacionais e collections JSON entre instâncias configuradas num domínio de Enterprise Replication
  • Dados espaciais
    • Melhorias no tratamento de dados espaciais
      Mais sistemas de referência e a possibilidade de calcular áreas e distâncias em dados baseados no modelo esférico da Terra.
      Os tipos de dados espaciais seguem agora a norma OpenGIS Simple Features Specification for SQL Revision 1.1 e a ISO/IEC 13249-3 SQL/MM Part 3: Spatial. A solução espacial Informix é baseada nas bibliotecas ESRI SDE 10.2 Shape e PE
  • Dados Timeseries
    • Armazenamento
      • Armazenamento eficiente para dados em séries numéricas e hertzianos
        Um elemento Timeseries compactado pode conter até 4KB de dados hertzianos (intervalos sub-segundo)
    • Containers
      • Controlo sobre o comportamento destrutivo de rolling window containersPodemos limitar o número de janelas que são destruídas numa só operação sobre um container em rolling window 
      • Monitorizar containers com caracteres wildcard
        Várias funções de monitorização passam a aceitar wildcards nos nomes dos Containers
  • Queries mais rápidas
    • Queries mais rápidas via execução paralela de rotinas
      As funções que podem ser usadas na cláusula WHERE das instruções SELECT podem ser executadas em paralelo (tirando proveito dos valores de PDQPRIORITY) se as tabelas estiverem fragmentadas
    • Queries mais rápidas nas condições IN sobre tabelas virtuais (VTI)
      Otimização de queries com IN
  • Warehousing
    • Tipos de dados adicionais
      • Aceleração de warehouse queries in-memory usando dados de múltiplas fontes
        Sinónimos e views usados para o IWA podem agora referencias tabelas em diferentes bases de dados, tabelas da mesma instância ou tabelas noutras instâncias. Dados JSON podem também ser usados

Para além da breve análise acima, gostaria de salientar alguns pontos:
  1. Buffer pool dinâmica. Apesar de atualmente "apenas" poder ser configurada para se auto-ajustar (crescerá se a percentagem de hits da cache de leitura ficar abaixo do limite por nós definido durante um intervalo de tempo), este será um passo histórico.
    Recordo-me de em várias ocasiões estar a explicar a transformação gradual dos parâmetros em dinâmicos (poderem ser mudados sem parar a instância), ter referido que "a BUFFERPOOL por exemplo possivelmente nunca será dinâmico". Parece que tenho de rever esta posição, ainda que de momento e no sentido estrito não o seja. Mas parece-me razoável pensar que no futuro poderemos mudar este parâmetro com um onmode -wm/-wf ou usando a SQL Admin API
  2. Poderá ter reparado que algumas das funcionalidades aqui descritas já tinham aparecido na 11.70.xC8. Isto acontece porque algumas mudanças de código podem ser transversais às versões, aparecendo na N-1 ou N conforme o ciclo normal de releases e o calendário. Nada que ver com marketing. Se a mudança de código é viável na versão N-1 nós fornece-mo-la aos clientes. Não obrigados a estar sempre na última versão se quiser beneficiar de algumas inovações. Quantos fornecedores incorporam funcionalidades novas em versões anteriores dos seus produtos?
  3. Melhorias contínuas em áreas que distinguem o Informix da concorrência: Timeseries, JSON e IWA.

Categorias: Blogs em Português

Tecnologia da Informação: HDR : Introdução

AJMoreti - sex, 07/03/2014 - 00:15

Tecnologia da Informação: HDR : Introdução: Esta seção abrange os seguintes tópicos relacionados à High Availability Data Replication: O que é HDR? Vantagens do HDR Disvantagens do...

Categorias: Blogs em Português

Re: RSS Authentication Failure

Forum Informix em Português do IIUG - qui, 06/03/2014 - 16:45

Ola Jairo, obrigado fiz essa alteração e deu certo SERVERNUM 0 DBSERVERNAME rss DBSERVERALIASES rsssoc para SERVERNUM 0 DBSERVERNAME rsssoc DBSERVERALIASES rss obrigado ******************************************************************************* To post a response via email (IIUG members only): 1. Address it to iiug-por@iiug.org 2. Include the bracketed message number in the subject line: [248] *******************************************************************************

Categorias: Forums de Informix em Português

HDR : Introdução

AJMoreti - qui, 06/03/2014 - 14:43

Esta seção abrange os seguintes tópicos relacionados à High Availability Data Replication:

  • O que é HDR?
  • Vantagens do HDR
  • Disvantagens do HDR

O que é HDR?

High Availability Data Replication (HDR)  é um método para replicar dados a partir de um servidor primário para outro servidor (secundário). HDR replica qualquer banco de dados registrados no servidor primário para um servidor secundário. Enquanto que o servidor secundário é considerado uma duplicação do servidor primário, ele não vai conter dados de bancos que esteja configurado em modo “non-logged”. O banco de dados e esquemas existem, as declarações DML (Data Manipulation Language) são sempre modo “logged”, mas todos os dados inseridos, atualizados, ou excluidos não será replicado a menos que o banco de dados esteja em modo “logged”. O HDR assegura que o servidor secundário é sempre sincronizado com o servidor primario. Se o primario falhar, o servidor secundario pode ser usado como backup até que o servidor primário esteja disponivel novamente.

Vantagens do HDR

  • Alta disponibilidade: se o servidor primário falhar, acesso de gravação pode ser habilitado automaticamente no servidor secundário.
  • (Potencial) alívio Capacidade: Com outra cópia de seus dados disponível, você poderá rodar relatórios no servidor secundario, em vez de executá-los no primário. Isso ajuda a aumentar a taxa de transferência do servidor primário.
  • Replicação síncrona ou assíncrona: Atualização sincrona é quando um buffer de logical log no servidor primário só é concluida após o buffer de logical logs ter sido copiado para o buffer de replicação de dados, enviado através da rede, e uma confirmação é enviada de volta para o servidor primário que o buffer foi recebido. Com a atualização sincrona, você terá certeza de que as transações confirmadas no servidor primario também foi enviada para o servidor secundário. Atualização assíncrona é onde o servidor primario copia os buffers de logical logs para o buffer de replicação de dados, em seguida faz  flush do buffer de logical logs e um envio dos conteúdos do buffer de replicação de dados através de rede quando qualquer dos seguintes eventos ocorrerem:

    - O buffer de replicação de dados tornam-se cheios
    - A aplicação comita uma transação em um banco de dados sem buffer
    - O intervalo de tempo especificado pela configuração do parâmetro DRINTERVAL é atingida.

Disvantagens do HDR

  • Banco de dados em modo de log: somente bancos de dados com o log habilitado será replicado .
  • Escopo do banco de dados: A granularidade do HDR está no nível de banco de dados. Todas tabelas em log serão replicados.
  • Modo síncrono: Com a atualização síncrona pode haver um pequeno atraso na liberação de um buffer de logical logs por causa do tráfego de rede.
  • Modo assíncrono: Com a atualização assíncrona há um potencial para algumas transações confirmadas no primário de não serem replicadas para o secundario após uma falha.
  • Suporte de blobspace: Qualquer armazenamento tipo blob não será replicado.
  • Secundário Read Only: O servidor secundário é um servidor somente de leitura. todas as aplicações que necessitam de acesso de gravação deve utilizar o primário.

Categorias: Blogs em Português

Re: RSS Authentication Failure

Forum Informix em Português do IIUG - qui, 06/03/2014 - 14:14

Lendo a mensagem de erro e a recomendação que você pegou no site da IBM entendi que o problema pode ser o fato do DBSERVERNAME estar associado ao modo de conexão via Shared Memory. Tente alternar os nomes da configuração DBSERVERNAME e DBSERVERALIASES e veja se dá resultado. Jairo Gubler ----- Mensagem original ----- De: "AMILCAR MORETI" <ajmoreti@yahoo.com.br> Para: iiug-por@iiug.org Enviadas: Quinta-feira, 6 de março de 2014 13:47:14 Assunto: RSS Authentication Failure [246] Saudaes, estou com um problema ao sincronizar um n RSS. informix@lnxbd03:/usr/informix/ifx1170FC4/etc> onstat -m IBM Informix Dynamic Server Version 11.70.FC4GE -- Fast Recovery -- Up 03:10:21 -- 6644708 Kbytes Message Log File: /var/log/idslog/rss.log 11:31:30 Checkpoint Completed: duration was 0 seconds. 11:31:30 Thu Mar 6 - loguniq 5580, logpos 0x5b5d690, timestamp: 0xe11660a5 Interval: 110843 11:31:30 Maximum server connections 0 11:31:30 Checkpoint Statistics - Avg. Txn Block Time 0.000, # Txns blocked 0, Plog used 0, Llog used 0 11:31:31 Physical Restore of rootdbs, dbsplog, dbsllog, prd Completed. 11:31:31 Checkpoint Completed: duration was 0 seconds. 11:31:31 Thu Mar 6 - loguniq 5580, logpos 0x5b5d690, timestamp: 0xe11660c1 Interval: 110843 11:31:31 Maximum server connections 0 13:07:44 DR: Reservation of the last logical log for log backup turned off 13:07:44 DR: new type = RSS 13:07:44 Unexpected RSS Authentication Failure during Initialization Phase 13:07:44 Data replication type and state information reset. To start DR, use the 'onmode -d' command and wait for the pair to be operational, before shutting down the database server 13:07:45 Defragmenter cleaner thread now running 13:07:45 Defragmenter cleaner thread cleaned:0 partitions o erro acima ocorre logo aps eu digitar o comando: onmode -d RSS prdsoc procurei no site da IBM, porem no resolveu meu problema, ou eu nao estou conseguindo, mas o site da IBM diz o seguinte: Unexpected RSS Authentication Failure during Initialization Phase when initializing RSS Technote (troubleshooting) Problem(Abstract) Error "Unexpected RSS Authentication Failure during Initialization Phase" when initializing a Remote Standalone Secondary (RSS) server. Symptom You try to start an RSS server but see this message in the online.log: Unexpected RSS Authentication Failure during Initialization Phase Cause The DBSERVERNAME is set to a shared memory connection in the ONCONFIG file. Note: For other possible causes of this error see the Related Information section. Resolving the problem Set the DBSERVERNAME to a tcp connection in the ONCONFIG file. Run your onmode -d RSS <primary servername> command. minha configurao: informix@lnxbd03:~> echo $INFORMIXSERVER rsssoc ONCONFIG SERVERNUM 0 DBSERVERNAME rss DBSERVERALIASES rsssoc NETTYPE ipcshm,1,50,CPU NETTYPE soctcp,3,150,NET SQLHOSTS rss onipcshm lnxbd03 rss rsssoc onsoctcp lnxbd03 rsssrv prdsoc onsoctcp lnxbd01 prdsrv fico agradecido pela ajuda desde ja. att -- Esta mensagem foi verificada pelo sistema de antivrus da Dgitro Tecnologia ******************************************************************************* To post a response via email (IIUG members only): 1. Address it to iiug-por@iiug.org 2. Include the bracketed message number in the subject line: [247] *******************************************************************************

Categorias: Forums de Informix em Português

RSS Authentication Failure

Forum Informix em Português do IIUG - qui, 06/03/2014 - 13:47

Saudações, estou com um problema ao sincronizar um nó RSS. informix@lnxbd03:/usr/informix/ifx1170FC4/etc> onstat -m IBM Informix Dynamic Server Version 11.70.FC4GE -- Fast Recovery -- Up 03:10:21 -- 6644708 Kbytes Message Log File: /var/log/idslog/rss.log 11:31:30 Checkpoint Completed: duration was 0 seconds. 11:31:30 Thu Mar 6 - loguniq 5580, logpos 0x5b5d690, timestamp: 0xe11660a5 Interval: 110843 11:31:30 Maximum server connections 0 11:31:30 Checkpoint Statistics - Avg. Txn Block Time 0.000, # Txns blocked 0, Plog used 0, Llog used 0 11:31:31 Physical Restore of rootdbs, dbsplog, dbsllog, prd Completed. 11:31:31 Checkpoint Completed: duration was 0 seconds. 11:31:31 Thu Mar 6 - loguniq 5580, logpos 0x5b5d690, timestamp: 0xe11660c1 Interval: 110843 11:31:31 Maximum server connections 0 13:07:44 DR: Reservation of the last logical log for log backup turned off 13:07:44 DR: new type = RSS 13:07:44 Unexpected RSS Authentication Failure during Initialization Phase 13:07:44 Data replication type and state information reset. To start DR, use the 'onmode -d' command and wait for the pair to be operational, before shutting down the database server 13:07:45 Defragmenter cleaner thread now running 13:07:45 Defragmenter cleaner thread cleaned:0 partitions o erro acima ocorre logo após eu digitar o comando: onmode -d RSS prdsoc procurei no site da IBM, porem não resolveu meu problema, ou eu nao estou conseguindo, mas o site da IBM diz o seguinte: Unexpected RSS Authentication Failure during Initialization Phase when initializing RSS Technote (troubleshooting) Problem(Abstract) Error "Unexpected RSS Authentication Failure during Initialization Phase" when initializing a Remote Standalone Secondary (RSS) server. Symptom You try to start an RSS server but see this message in the online.log: Unexpected RSS Authentication Failure during Initialization Phase Cause The DBSERVERNAME is set to a shared memory connection in the ONCONFIG file. Note: For other possible causes of this error see the Related Information section. Resolving the problem Set the DBSERVERNAME to a tcp connection in the ONCONFIG file. Run your onmode -d RSS <primary servername> command. minha configuração: informix@lnxbd03:~> echo $INFORMIXSERVER rsssoc ONCONFIG SERVERNUM 0 DBSERVERNAME rss DBSERVERALIASES rsssoc NETTYPE ipcshm,1,50,CPU NETTYPE soctcp,3,150,NET SQLHOSTS rss onipcshm lnxbd03 rss rsssoc onsoctcp lnxbd03 rsssrv prdsoc onsoctcp lnxbd01 prdsrv fico agradecido pela ajuda desde ja. att ******************************************************************************* To post a response via email (IIUG members only): 1. Address it to iiug-por@iiug.org 2. Include the bracketed message number in the subject line: [246] *******************************************************************************

Categorias: Forums de Informix em Português

Tecnologia da Informação: cpio

AJMoreti - qui, 27/02/2014 - 11:32

Tecnologia da Informação: cpio: faz cópia (salva e restaura) de arquivos através de listas. SINTAXE: cpio –o [a m c B v] cpio –i [c v B d u m] cpio –p [a m d v u] dire...

Categorias: Blogs em Português

cpio

AJMoreti - qui, 27/02/2014 - 11:09

faz cópia (salva e restaura) de arquivos através de listas.

SINTAXE:

cpio –o [a m c B v]

cpio –i [c v B d u m]

cpio –p [a m d v u] diretório

DESCRIÇÃO:

-o Lê arquivos de entrada padrão gravando tais arquivos na saída especificada pelo redirecionamento (output) -i Extrai arquivos de entrada padrão para o diretório corrente (input) -p Copia arquivos de entrada padrão para um diretório.

 

FLAGS

-m Mantém a data de modificação do arquivo -a A data do último acesso do arquivo é alterada para a data corrente -B A entrada/saída é manipulada em blocos de 512 bytes; se omitido este flag, o buffer default é de 512 bytes -c Grava informações do header da lista de arquivos em formato ASCII, visando a portabilidade para outros sistemas UNIX. -d Diretórios são criados se necessário -v Exibe o nome dos arquivos que estão sendo copiados ou restaurados -t Informa a tabela de conteúdo da lista, mas nenhum arquivo será copiado -u Copia incondicional; por default um arquivo antigo não substitui um arquivo novo com o mesmo nome. Com esta opção será possivel device Nome do arquivo especial caracter que representa o device

 

COMENTÁRIOS:

- o cpio só restaura arquivos que foram copiados por ele.

- o cpio é utilizado para fazer cópias através de listas de nomes de arquivos, trabalhando com a entrada e saída.

EXEMPLOS:

Cópias de disco para mídias

# ls | cpio –ocvB > /dev/rmt/0m

# find . –print | cpio –ovB > /dev/st0

# cpio –ocvB <lista> > /dev/rmt/0m

# cat lista |cpio –ovB > /dev/st0

# ls |cpio –ovcB > /dev/rmt/0m

Listar arquivos da mídia

# cpio –itvcB < /dev/rmt/0m

# cpio –itvB < /dev/st0

# cpio –itv < /dev/st0

Restaurar arquivos da mídia

# cpio –ivcB < /dev/rmt/0m

# cpio –iv < /dev/st0

# cpio –iv “arq” < /dev/rmt/0m

# cpio – ivBc < /dev/rmt/0m

# cpio –ivu < /dev/st0

Cópia para diretórios

# find . –print | cpio –pd /home/ajmsolutions/dir

Categorias: Blogs em Português

New feature? / Nova funcionalidade?

Informix-Techonology - ter, 18/02/2014 - 21:27

This article is written in English and Portuguese (original version here)
Este artigo está escrito em Inglês e Português (versão original aqui)


English version:
The fun... Working with Informix has been much fun, but sometimes for strange reasons. One of those is the consequence of not being considered a "mainstream" database. From time to time I see references to features of "mainstream" databases that do amuse me. It surely happens the other way around, but the echoes of that are naturally smaller and could be expected from what bloggers and analysts would consider a non-Tier 1 database. So, when this happens It really makes my day...
This time, while browsing the Net, I noticed several articles or posts talking about a "new fascinating feature" of SQL Server 2014 CTP2 (pre-release) called Delayed Transaction Durability. Well... After reading some of those posts including the "official source" I was a bit surprised that this is just what we call buffered logging! Yes... The ability to delay logical log buffer flush until the logical log buffer is full. And yes, same as us, it means that an application can assume something is committed while if there is a database crash it won't (the fast recovery process will rollback the transaction).
So, as you may expect, most bloggers were careful to note this could lead to data loss. In fact the database integrity is preserved, but because the application receives the commit "ok" message before it was actually written to disk, a crash in the specific interval would mean the transaction would be incomplete so a rollback would happen during recovery.
So why use it? Performance... But in fact most customers don't want to risk and they usually choose unbuffered logging.
What we have But why am I writing this? Just to make fun of our competitor and the fact that they're announcing and talking about a feature that everybody else (Informix, Oracle, DB2, MySQL, Postgres...) seems to have? Not really... although it is funny to see this situation over and over again (happened recently with SQL Server's high availability options also...).
The fact is that there's more to this than what immediately comes to mind. First, I'd bet most of our customers know about the [BUFFERED] LOG option of the CREATE DATABASE statement, but they possibly don't know about the SET [BUFFERED] LOG statement. Did I catch you? Read on... Secondly, because there are at least two databases that implemented this better than us (and I find it very little ambitious from Microsoft to implement just what I'd call "basic" - if you're doing something new, you may as well aim for the best available). So, let's start by the SQL statement SET [BUFFERED] LOG.
I could track this down to at least version 7.3 of Informix Dynamic Server's (1998) documentation as well as the Online engine. And this matches more or less the functionality that SQL Server is implementing now (16 years later, not bad, right?). It means that even in an unbuffered database, you can ask the server to work with your session as if it was setup for BUFFERED logging. In other words, COMMITs issued by sessions that execute SET BUFFERED LOG won't cause the flush of the logical log buffer to disk. They will behave as if you had created the database with BUFFERED LOG. Consequently you're possibly contributing to the database performance, while you open the window to "data loss" only in your session.
Alternatively you can execute the SET LOG statement and ask the server to flush the logical log buffer on every COMMIT you make. You'll make sure that your commits are persisted to disk even if you're working on a BUFFERED database.
We can see the effect of this statement quite easily. The test case I created is fairly simple:

  1. Create a very simple table with an ID (INTEGER) and some other column - VAL (CHAR(1)) - with 1M rows in an UNBUFFERED LOG database
  2. Create a procedure that accepts the number of records to update, the commit interval and the new value
  3. Reset the engine counters (onstat -z)
  4. Set either BUFFERED or UNBUFFERED LOG level for the session
  5. Execute the procedure with some values
  6. Check the statistics with onstat -l
  7. Repeat from 3 using a different logging mode and compare the times and specially the counter values
So let's do it. The table and procedure SQL is this:
castelo@primary:informix-> cat test_buf.sql
DROP PROCEDURE IF EXISTS test_proc;
DROP TABLE IF EXISTS test_data;
SELECT LEVEL id,"A" val FROM sysmaster:sysdual CONNECT BY LEVEL <= 1000000 INTO RAW test_data IN dbs1 EXTENT SIZE 5000 NEXT SIZE 5000;
ALTER TABLE test_data TYPE(standard);

CREATE PROCEDURE test_proc(total_rec INTEGER, commit_interval INTEGER, new_value CHAR) RETURNING INTEGER;

DEFINE total_counter, commit_counter, v_id, cycle INTEGER;

LET total_counter=0;
LET commit_counter=0;
LET cycle = 0;

BEGIN WORK;
FOREACH c1 WITH HOLD FOR
SELECT
id
INTO v_id
FROM
test_data

UPDATE test_data SET val = new_value WHERE CURRENT OF c1;
LET total_counter = total_counter + 1;
LET commit_counter = commit_counter + 1;
IF commit_counter = commit_interval
THEN
LET cycle = cycle + 1;
COMMIT WORK;
LET commit_counter = 0;
BEGIN WORK;
END IF;
IF total_counter = total_rec
THEN
COMMIT WORK;
RETURN cycle;
END IF
END FOREACH;

END PROCEDURE;
Now, let's try it with a COMMIT interval of 100 records and UNBUFFERED LOG. The code and output is this:
castelo@primary:informix-> dbaccess -e stores run_unbuf.sql

Database selected.

SET LOG;
Log set.


EXECUTE FUNCTION sysadmin:task('onstat', '-z');


(expression)
IBM Informix Dynamic Server Version 12.10.FC2 -- On-Line -- Up 09
:01:52 -- 287720 Kbytes



1 row(s) retrieved.


SELECT CURRENT YEAR TO FRACTION FROM systables WHERE tabid = 1;

(expression)

2014-02-17 18:57:09.622

1 row(s) retrieved.


EXECUTE PROCEDURE test_proc(500000,100,'U');

(expression)

5000

1 row(s) retrieved.


SELECT CURRENT YEAR TO FRACTION FROM systables WHERE tabid = 1;

(expression)

2014-02-17 18:57:20.242

1 row(s) retrieved.



Database closed.
It took around 11-12s but the real important part is this:
castelo@primary:informix-> onstat -l

IBM Informix Dynamic Server Version 12.10.FC2 -- On-Line -- Up 09:02:15 -- 287720 Kbytes

Physical Logging
Buffer bufused bufsize numpages numwrits pages/io
P-2 15 64 14 0 0.00
phybegin physize phypos phyused %used
2:53 62500 50947 44 0.07

Logical Logging
Buffer bufused bufsize numrecs numpages numwrits recs/pages pages/io
L-2 0 64 510094 20018 5015 25.5 4.0
Subsystem numrecs Log Space used
OLDRSAM 510094 38569892

Note that we've done 5015 write operations to disk. On each of them, on average we were writing four pages of logical log buffer. Each page contains around 25 records, so as we've asked for a COMMIT interval each 100 rows, everything matches what we'd expect.
Let's try with BUFFERED LOG:
castelo@primary:informix-> dbaccess -e stores run_buf.sql

Database selected.

SET BUFFERED LOG;
Log set.


EXECUTE FUNCTION sysadmin:task('onstat', '-z');


(expression)
IBM Informix Dynamic Server Version 12.10.FC2 -- On-Line -- Up 09
:07:52 -- 287720 Kbytes



1 row(s) retrieved.


SELECT CURRENT YEAR TO FRACTION FROM systables WHERE tabid = 1;

(expression)

2014-02-17 19:03:08.698

1 row(s) retrieved.


EXECUTE PROCEDURE test_proc(500000,100,'B');

(expression)

5000

1 row(s) retrieved.


SELECT CURRENT YEAR TO FRACTION FROM systables WHERE tabid = 1;

(expression)

2014-02-17 19:03:17.004

1 row(s) retrieved.



Database closed.
It took 8-9s, so it's a bit faster, but this is a VM, with no more activity... But the more interesting part is the logical log statistics we get from onstat -l:

castelo@primary:informix-> onstat -l

IBM Informix Dynamic Server Version 12.10.FC2 -- On-Line -- Up 09:10:44 -- 287720 Kbytes

Physical Logging
Buffer bufused bufsize numpages numwrits pages/io
P-1 18 64 17 0 0.00
phybegin physize phypos phyused %used
2:53 62500 50974 29 0.05

Logical Logging
Buffer bufused bufsize numrecs numpages numwrits recs/pages pages/io
L-2 0 64 510095 19119 313 26.7 61.1
Subsystem numrecs Log Space used
OLDRSAM 510095 38570264


Let's compare both outputs:
  • The number of records and log space used it roughly the same
  • The number of records per page is roughly the same
  • The number of writes (313) is much less than for UNBUFFERED mode (5015)
  • The number of pages on each write (average) if much higher now (61.1) as opposed to 4 in the previous test (I had a LOGBUFF size of 128KB)
What we're missing Now, let's look at the other more interesting aspect of this.... I mentioned earlier that at least two databases do this in a smarter way than Informix. I'm thinking about DB2 and Oracle. How can this be done in a smarter way? Well, as you notice, the BUFFERED logging is a trade-off. You exchange security for performance (you give away the first and gain on the second). What if there was a better solution? What if you could gain on I/O performance, by reducing the number of operations while not giving away the durability of your data? It may seem impossible, but it's actually very easy and has been done. Let's assume what we have right now in most customers:
  • Lots of sessions and most of them do a commit from time to time
  • Many sessions making commits from time to time, usually means a very frequent commit rate
  • Very frequent commit rates means that we'll do a lot of logical log flushes per second. This is usually noticeable from the average pages per logical log flush. On busy systems with UNBUFFERED LOG this tends to be 1
The way other RDBMs can be configured is to don't flush on every commit but:
  1. Flush when the buffer is full (this always happen)
  2. Flush the logical log buffer if an amount of time has elapsed since the last flush, or flush only after a specific number of COMMITS have been issued
  3. Only send the ok to the application after you effectively flush the buffer that contains the COMMIT
This may seem a bit strange, because you're effectively "holding back" the applications. But keep in mind that this delay can be very small and is optional. The difference between this and the BUFFERED LOG is that the application will probably get only a slight delay, but more importantly, it won't receive an OK of an "uncommitted" COMMIT. When it gets the "ok", the data is securely flushed to the logical logs. Assuming this can be configure at the session level, we can get the best of both worlds (as there  is no gain without loss, the loss here is the probably slight delay, which for interactive applications at least would be unnoticeable)

I've seen situations where the I/O rate on the logical logs can be a bottleneck. As such I've created an RFE (Request For Enhancement) 45166 . If you like the idea and you've seen this happen on your system, vote for it


Versão Portuguesa:
A parte engraçada... Trabalhar com Informix tem sido bastante engraçado, mas por vezes é por razões estranhas. Uma delas é a consequência de não ser considerada uma base de dados mainstream. De tempos a tempos vejo referências a "novas" funcionalidades nas bases de dados mais populares que me fazem sorrir. Certamente que o contrário também acontece, mas os ecos dessas "novas" funcionalidades no Informix são sempre menores que nos outros,  e seria algo normal numa base de dados que muitos bloggers e analistas não consideram Tier-1. Portanto quando tal acontece, ganho o dia...
Desta feita, ao navegar pela Internet, reparei em vários artigos referindo-se a (minha tradução) "funcionalidade nova e fascinante" do SQL Server 2014 CTP2 (ante-visão) chamada Delayed Transaction Durability. Bom... Depois de ler alguns destes artigos, incluindo o "oficial" fiquei um pouco surpreendido que isto seja apenas aquilo a que chamamos buffered logging! Sim... A possibilidade de atrasar o flush do buffer do logical log até que o mesmo buffer se encontre cheio, em vez de o fazer a cada COMMIT. E sim, tal como nós, isto significa que a aplicação assume que algo foi efetivamente "COMMITed", ao passo que se houver uma queda inesperada da base de dados na verdade não foi (o processo de fast recovery irá fazer rollback da transação).
Portanto, como seria de esperar, muitos autores de blogs foram cautelosos e referiram que isto pode levar à perda de dados. Na verdade a integridade da base de dados é mantida, mas como a aplicação recebe o "ok" antes de os dados estarem efetivamente escritos em disco, uma queda num intervalo específico, significaria que a transação estaria incompleta , levando portanto a um rollback durante o processo de recovery.
Então para quê usar isto? Rapidez... Mas na realidade a maioria dos clientes não quer arriscar, e habitualmente escolhem unbuffered logging.
A parte que temosMas porque estou a escrever isto? Apenas para brincar com a concorrência, realçando o facto de estarem a anunciar uma funcionalidade que todas a bases de dados (Informix, DB2, Oracle, MySQL, Postgres...) já têm? Não... não é por isso. Apesar de ser divertido verificar este tipo de situações com frequência (aconteceu recentemente com as suas funcionalidades de alta disponibilidade...).
O ponto é que este assunto tem outros aspetos interessantes para além do óbvio. Primeiro, apostaria que a maioria dos nossos clientes conhecem a opção [BUFFERED] LOG da instrução CREATE DATABASE. mas possivelmente desconhecem a instrução SET [BUFFERED] LOG. Apanhei-o? Continue a ler... Em segundo, existem pelo menos duas bases de dados que implementaram isto de forma mais "inteligente" que o Informix (e parece-me pouco ambicioso da parte da Microsoft fazer a implementação "básica" - se vamos criar algo de novo, porque não apontar para o melhor possível?). Veremos como isso pode ser feito e quais as diferenças. Comecemos então pela instrução SET [BUFFERED] LOG.
Consegui encontrar referências a esta instrução pelo menos tão antigas quanto a versão 7.3 do Informix Dynamic Server (1998), e também na documentação do motor Online. E isto mapeia mais ou menos diretamente com a funcionalidade que o SQL Server está a receber agora (16 anos depois não é mau, certo?). Significa que mesmo numa base de dados criada com unbuffered logging, podemos pedir ao servidor que trabalhe na nossa sessão como se estivesse em BUFFERED LOG. Por outras palavras, o COMMIT efetuado por sessões que executem o SET BUFFERED LOG, não força o flush do buffer do logical log. As sessões comportam-se como se tivéssemos criado a base de dados com BUFFERED LOG. Assim estaremos a contribuir para o aumento da performance da base de dados, ao mesmo tempo que limitamos a possibilidade de "perda de dados" apenas à(s) sessão que executou esta instrução.
Noutro cenário podemos executar a instrução SET LOG e pedir ao servidor que faça o flush do logical log buffer em cada COMMIT que façamos. Garantiremos que todos os nossos COMMITs são escritos em disco, antes de recebermos o "ok", mesmo que a base de dados esteja em modo BUFFERED.
Podemos ver o efeito desta instrução de forma bastante fácil. O caso de teste que criei é bastante simples:
  1. Criar uma tabela muito simples com um ID (INTEGER) e uma outra coluna - VAL (CHAR(1)) - com 1M de registos numa base de dados criada com UNBUFFERED LOG
  2. Criar um procedimento que aceita o número de registos a alterar, o intervalo de COMMIT e um novo valor para a coluna VAL
  3. Fazer o reset dos contadores do motor (com onstat -z)
  4. Estabelecer o modo BUFFERED ou UNBUFFERED LOG na nossa sessão
  5. Executar o procedimento com certos valores
  6. Verificar as estatísticas com onstat -l
  7. Repetir a partir do ponto 3 usando um modo de LOG diferente e comparar os tempos e mais importante os valores dos contadores
Vamos lá então fazê-lo. A tabela e o procedimento são os seguintes:
castelo@primary:informix-> cat test_buf.sql
DROP PROCEDURE IF EXISTS test_proc;
DROP TABLE IF EXISTS test_data;
SELECT LEVEL id,"A" val FROM sysmaster:sysdual CONNECT BY LEVEL <= 1000000 INTO RAW test_data IN dbs1 EXTENT SIZE 5000 NEXT SIZE 5000;
ALTER TABLE test_data TYPE(standard);

CREATE PROCEDURE test_proc(total_rec INTEGER, commit_interval INTEGER, new_value CHAR) RETURNING INTEGER;

DEFINE total_counter, commit_counter, v_id, cycle INTEGER;

LET total_counter=0;
LET commit_counter=0;
LET cycle = 0;

BEGIN WORK;
FOREACH c1 WITH HOLD FOR
SELECT
id
INTO v_id
FROM
test_data

UPDATE test_data SET val = new_value WHERE CURRENT OF c1;
LET total_counter = total_counter + 1;
LET commit_counter = commit_counter + 1;
IF commit_counter = commit_interval
THEN
LET cycle = cycle + 1;
COMMIT WORK;
LET commit_counter = 0;
BEGIN WORK;
END IF;
IF total_counter = total_rec
THEN
COMMIT WORK;
RETURN cycle;
END IF
END FOREACH;

END PROCEDURE;
Vamos tentar com um intervalo de de COMMIT e UNBUFFERED LOG. O código e o resultado é o seguinte:
castelo@primary:informix-> dbaccess -e stores run_unbuf.sql

Database selected.

SET LOG;
Log set.


EXECUTE FUNCTION sysadmin:task('onstat', '-z');


(expression)
IBM Informix Dynamic Server Version 12.10.FC2 -- On-Line -- Up 09
:01:52 -- 287720 Kbytes



1 row(s) retrieved.


SELECT CURRENT YEAR TO FRACTION FROM systables WHERE tabid = 1;

(expression)

2014-02-17 18:57:09.622

1 row(s) retrieved.


EXECUTE PROCEDURE test_proc(500000,100,'U');

(expression)

5000

1 row(s) retrieved.


SELECT CURRENT YEAR TO FRACTION FROM systables WHERE tabid = 1;

(expression)

2014-02-17 18:57:20.242

1 row(s) retrieved.



Database closed. Demorou à volta de 11-12s, mas a parte mais importante é esta:
castelo@primary:informix-> onstat -l

IBM Informix Dynamic Server Version 12.10.FC2 -- On-Line -- Up 09:02:15 -- 287720 Kbytes

Physical Logging
Buffer bufused bufsize numpages numwrits pages/io
P-2 15 64 14 0 0.00
phybegin physize phypos phyused %used
2:53 62500 50947 44 0.07

Logical Logging
Buffer bufused bufsize numrecs numpages numwrits recs/pages pages/io
L-2 0 64 510094 20018 5015 25.5 4.0
Subsystem numrecs Log Space used
OLDRSAM 510094 38569892

Repare que fizemos 5015 operações de escrita em disco. Em cada uma delas, em média, escrevemos 4 páginas do logical log buffer. Cada página contém em média 25 registos (de log), portanto como pedimos COMMITs de 100 em 100 registos os valores batem certo com o que seria expectável.
Vamos tentar com BUFFERED LOG:
castelo@primary:informix-> dbaccess -e stores run_buf.sql

Database selected.

SET BUFFERED LOG;
Log set.


EXECUTE FUNCTION sysadmin:task('onstat', '-z');


(expression)
IBM Informix Dynamic Server Version 12.10.FC2 -- On-Line -- Up 09
:07:52 -- 287720 Kbytes



1 row(s) retrieved.


SELECT CURRENT YEAR TO FRACTION FROM systables WHERE tabid = 1;

(expression)

2014-02-17 19:03:08.698

1 row(s) retrieved.


EXECUTE PROCEDURE test_proc(500000,100,'B');

(expression)

5000

1 row(s) retrieved.


SELECT CURRENT YEAR TO FRACTION FROM systables WHERE tabid = 1;

(expression)

2014-02-17 19:03:17.004

1 row(s) retrieved.



Database closed.
Demorou 8-9s, por isso foi um pouco mais rápido, mas isto é uma máquina virtual sem mais actividade... Mas a parte mais interessante são as estatísticas do logical log que obtemos com o onstat -l:

castelo@primary:informix-> onstat -l

IBM Informix Dynamic Server Version 12.10.FC2 -- On-Line -- Up 09:10:44 -- 287720 Kbytes

Physical Logging
Buffer bufused bufsize numpages numwrits pages/io
P-1 18 64 17 0 0.00
phybegin physize phypos phyused %used
2:53 62500 50974 29 0.05

Logical Logging
Buffer bufused bufsize numrecs numpages numwrits recs/pages pages/io
L-2 0 64 510095 19119 313 26.7 61.1
Subsystem numrecs Log Space used
OLDRSAM 510095 38570264

Vamos comparar ambos os outputs:
  • O número de registos e o espaço em log é praticamente o mesmo
  • O número de registos por página é praticamente o mesmo
  • O número de escritas (313) é muito menos que o efetuado em modo UNBUFFERED  (5015)
  • O número de páginas escritas em cada operação (média) é muito maior (61.1) do que o teste anterior que deu 4 páginas por escrita (tinha o LOGBUFF definido como 128KB)
O que nos falta Bom, vamos agora ver outro aspecto importante deste assunto... Como referi, existem pelo menos duas bases de dados que fazem isto de forma mais eficiente e interessante que o Informix. Estou a pensar no DB2 e no Oracle. Como é que isto pode ser feito de forma mais inteligente? Bom, como terá reparado, o BUFFERED logging é uma troca. Trocamos segurança por rapidez (damos a primeira e recebemos a segunda). Mas e se houver uma melhor solução? E se conseguissemos ganhar rapidez (fazendo menos I/O) e ao mesmo tempo não abdicar da segurança que a escrita prévia nos dá? Pode parecer impossível, mas na verdade pode ser muito fácil e já foi feito. Vamos assumir o cenário que encontro em muitos clientes atualmente:
  • Muitas sessões e a maioria delas fazem um COMMIT de vez em quando
  • Muitas sessões a fazerem COMMIT "de vez em quando", traduz-se normalmente num ritmo bastante alto de COMMITs
  • Um ritmo de COMMITs muito alto implica que façamos muitos flushes do logical log buffer por segundo. Isto é normalmente visível pelo número médio de páginas escritas em cada operação de I/O (flush), que em sistemas configurados em UNBUFFERED e com bastante actividade tende a ser 1
A forma como outras RDBMS podem ser configuradas é não fazer flush em cada COMMIT, mas:
  1. Fazer o flush quando o bufer enche (isto acontece sempre)
  2. Fazer o flush do logical log biffer se passou um determinado tempo desde o último flush, ou fazer o flush após um certo número de COMMITS terem sido executados
  3. Apenas enviar o "ok" às aplicacções quando fazemos efectivamente o flush do biuffer que contém o COMMIT por elas executado
Isto pode parecer um pouco estranho, porque estamos efetivamente a "atrasar" as aplicações. Mas considere que este atraso é muito pequeno e opcional. A diferença entre isto e o BUFFERED LOG é que a aplicação apenas sofre um ligeiro atraso, mas mais importante não recebe "ok" de um COMMIT que efectuou mas que ainda não foi garantido em disco. Quando recebe o "ok" é certo que os registos do log já foram escritos e persistidos em disco. Tendo em conta que isto pode ser configurado ao nível da sessão, podemos obter o melhor dos dois mundos (embora como não haja ganhos sem perdas, a perda aqui será o pequeno atraso, mas este para aplicações interativas é provavelmente negligenciável)

Já vi situações onde o ritmo de operações de I/O nos logical logs pode ser um "fúnil". Dái ter registado um RFE (Request For Enhancement) 45166 . Se gosta da ideia e já viu o mesmo acontecer no seu sistema vote neste pedido.

Categorias: Blogs em Português

Re: Problema no dbexport

Forum Informix em Português do IIUG - seg, 17/02/2014 - 10:08

Ola Cesar, td bem? satisfação... Obrigado pelo retorno, as versões eram a mesma sim, ja descobri o problema.. foi uma tabela com espaco entre os nomes, fiz um trace do export e peguei o erro. mesmo assim, mais uma vez fico muito agradecido a vc e a todos no grupo que me ajudaram a raciocinar. obrigado a todos. att ******************************************************************************* To post a response via email (IIUG members only): 1. Address it to iiug-por@iiug.org 2. Include the bracketed message number in the subject line: [245] *******************************************************************************

Categorias: Forums de Informix em Português

Re: Problema no dbexport

Forum Informix em Português do IIUG - sex, 14/02/2014 - 18:40

apenas por desencargo de conciencia.. . A versão do dbexport e do banco são a mesma? Tenho problemas com dbschema entre versões de banco (11.50 x 11.70)... dbexport não já sei dizer, porque não costumo utiliza-lo... Em 14 de fevereiro de 2014 14:30, AMILCAR MORETI <ajmoreti@yahoo.com.br>escreveu: > Obrigado pelo retorno Jairo, > > é muito estranho mesmo, mas olha só o que esta acontecendo, novamente eu > coloquei o dbexport para rodar, so que dessa vez eu estartei o trace pelo > onmode para "debugar" o erro. > > onmode -I 201 > > olha onde acontece o erro olhando para o arquivo af... > > Sess SQL Current Iso Lock SQL ISAM F.E. > Id Stmt type Database Lvl Mode ERR ERR Vers Explain > 106 logix CR Not Wait -201 0 9.24 Off > > Current SQL statement (851580) : > select count(*) from only("informix"."OLE DB Destination") > > Last parsed SQL statement : > select count(*) from only("informix"."OLE DB Destination") > > agora o que me chama atenção é que não existe essa tabela "informix"."OLE > DB > Destination" > > alguem sabe o que é isso? > > > > > > > --f46d043d67298d6c0804f264a847 ******************************************************************************* To post a response via email (IIUG members only): 1. Address it to iiug-por@iiug.org 2. Include the bracketed message number in the subject line: [244] *******************************************************************************

Categorias: Forums de Informix em Português