3 Temmuz 2012 Salı

PostgreSQL ile Random Kod Üretimi


Geçen günlerde lazım oldu, bir durum için 15 milyon adet random 11 karakterli bir text oluşturmam gerekti. Ufak bir script(bash yada c ile) üretmekte bir seçecekti fakat bir script ile 15 milyon unique satır oluşturmanın bazı zorlukları var. Kısaca bunları sıralayayım ki bir daha aynı şeyleri düşünmek zorunda kalmayalım.

  1. Tekilliğin (unique) sağlanması için her bir kod üretildikten sonra ya bir array,map, dict gibi birşeyde tutulup kontrol edilecek
  2. ikinci bir seçenek her ürettiğimiz kodu gidip veritabanına bu var mı? diye soracak bir select sorgusu olacak. (her seferinde db'ye açılacak bağlantılardan bahsetmiyorum bile)
  3. unique olmasını sağladığına emin olduğumuz bir algoritma yazılacak.
açıkcası bunlardan bana en matıklı geleni doğru dürüst bir algoritma yazmak, lakin bunun üretileceği tabloda daha önce herhangi bir kurala uygun olmadan üretilmiş 100 milyon kod daha olduğunu düşünürsek, yazacağımız algoritma biraz kasacaktı - ki bu işin 3 saat içinde bitmesi gerekiyordu.

Bu yukarda yazdığım sebeplerden mütevellit aşağıda bu işi direk olarak db üzerinde halleden basit bir pl/pgsql scripti var. biraz açıklamaya çalışarak paylaşayım istedim.


Öncelikle test tablomuzu yaratalım.

CREATE TABLE random_code (code varchar(11));

ilk fonksiyonumuz random bir text üreten fonksiyon.
Aldığı parametreler;

  • prefix : her kodun başına ekleyeceğimiz bir işaret ('TEST','XXX' gibi)
  • code_length : üretilecek kodun uzunluğu (prefix dahil)
farkettiğiniz üzere karakter listesinde 0 ve sesli harfler yok bunun sebebi, 0 rakamı 'o' harfi ile karıştırılabiliyor, sesli harfler ise random olarak bir araya geldiklerinde 'GÖT','MEME' gibi çokta 
insanlara vermek istemeyeceğiniz kodlar üretebiliyor. Bu yüzden onlarıda çıkardım. bu metodun kalan kısmı angarya.


CREATE OR REPLACE FUNCTION create_code(prefix varchar,code_length integer)
RETURNS text AS
$$
DECLARE
  chars text[] := '{1,2,3,4,5,6,7,8,9,B,C,D,F,G,H,J,K,L,M,N,P,Q,R,S,T,V,W,X,Y,Z}';
  result text := '';
  i integer := 0;
BEGIN
IF code_length < 1 then
    RAISE EXCEPTION 'Kod uzunlugu 1 den buyuk olmalıdır';
  END IF;

  result := result || prefix;

  FOR i IN 1.. code_length - code_length(prefix) LOOP
    result := result || chars[1+random()*(array_length(chars, 1)-1)];
  END LOOP;
  RETURN result;
END;
$$ language plpgsql;



Bütün olayın döndüğü yer aslında burası. Aldığı parametreleri açıklayayım;

  • count : üretilecek kupon adeti
  • prefix : her kodun başına ekleyeceğimiz bir işaret ('TEST','XXX' gibi)
  • code_length : üretilecek kodun uzunluğu (prefix dahil)
şimdi burada declare'in altında 2 tane değişken tanımlamışız, code_l ve temp_rec. code_l üretilen ve yazılacak olan kodun son hali, temp_rec üretilen kodun daha önce var olup olmadığını tutan 0 ya da 1 olabilen bir değişken.

gelelim olaya, ilk olarak temp bir tablo yaratıyorum. CREATE TEMP TABLE bu tabloda bir session içinde yaratılacak tüm kodları geçici olarak yazacağım. Bunun amacı, üretilen her bir kodun daha önceden üretilip üretilmediğini 100 milyon satır içeren bir tablodansa daha küçük bir tabloda aramak. Arkasından gelen CREATE INDEX ifadesini tahmin edersiniz sanrım. Burada kafanıza eğer 'Acaba bu index temp tablo gidince düşecek mi ?' sorusu geliyorsa, rahat olun düşecek o da.


Devam edersek, her bir kod'u WHILE döngüsü yazarak, eğer kod daha önceden yazılmışsa yazılmamışı bulana kadar yeniden üretmek için yapıyorum. daha sonrasında her üretilen kodu kesinlikle temp tabloya ve arkasından master tablomuza yazıyorum.

CREATE OR REPLACE FUNCTION generate_randomcode
(
count integer,
prefix text,
code_length integer
)
RETURNS void AS
$$
DECLARE
code_l text;
temp_rec int;
BEGIN

CREATE TEMP TABLE codes_tmp(a text) WITHOUT oids ON commit DROP;
CREATE INDEX temp_code_idx ON codes_tmp(a);
    FOR c IN 1..count LOOP
        code_l = create_code(prefix,code_length);
        SELECT count(*) INTO temp_rec FROM codes_tmp WHERE a = code_l;

        WHILE temp_rec != 0 LOOP
                code_l = create_code(prefix,code_length);
                SELECT COUNT(*) INTO temp_rec FROM codes_tmp WHERE a = code_l;
        END LOOP;
        INSERT INTO codes_tmp VALUES (code_l);
        INSERT INTO random_code (code) VALUES (code_l);
        END LOOP;
END;
$$ LANGUAGE 'plpgsql';

Tüm bu olan biteni anladıktan sonra,
SELECT generate_randomcode(10,'TEST',8);
dediğimizde, 8 karakterli TEST ile başlayan toplamda 10 adet kodumuz olmuş olacak.

25 Haziran 2012 Pazartesi

Automatic partitioning in PostgreSQL



This is just for test purposes. Do not use in production environment. As Pavel stated it will be terrible under high load. This post is just to show
it is possible to do it but not recommended. 

Last week I eed to do a partitioning on a big table but the problem is, i need to create partition for everyday. Everyday more than 15M records will be inserted into this table so, I try to automatic partitions. I don't mean creating partitions with cron or other system tools. I need to create partitions on postgreSQL using sql. So I use triggers and some system tables and it's done.

Above example is aim to do followings,

  • Create tables and data for testing purposes
  • Create partition tables automatically so when new record is inserted, if it's appropriate table is not found, create it.
  • After creating appropriate partition table then insert data to corresponding, partition

Let's start with creating tables and a function to create data for testing,

--create database first
--create  tables
--create database first
create database icecream_co_2;
comment on database icecream_co is 'daily partition test';
--create  tables
create table market (id serial primary key,name varchar(50));
--insert data for a market
insert into market (name) values ('test');
create table sales(id serial primary key, market_id integer references market(id),amount real ,created_at date);

Now we need data function (i prefer to write in pl/pgsql you can write where you want to) This function will insert data to sales table having created_at column (we will use this column in partitioning) is date. Our test data is starting from 01.06.2012 to 30.06.2012, for each day it executes 100 insert statements.
CREATE OR REPLACE FUNCTION createDailyDataForPartition() RETURNS void  AS
$$
DECLARE
    c date;
BEGIN
    FOR c IN SELECT CAST(d AS date) FROM generate_series(CAST('2012-06-01' AS timestamp with time zone),'2012-06-30','1 day') AS g(d)
    LOOP
        FOR i IN 1..100 LOOP
            insert into sales (amount,market_id,created_at) values (cast(i as real),1,cast(c as date));
        END LOOP;
    END LOOP;
END;
$$ LANGUAGE 'plpgsql' ;

Ok. We will use this function after creating partitions. Now.. here is the function that automatically creates partitions.
CREATE OR REPLACE FUNCTION create_daily_partition_tables(partition_id date) RETURNS void AS
$$
BEGIN

EXECUTE format('CREATE TABLE sales_%s (
    CHECK ( created_at >= %L AND created_at <= %L)
    ) INHERITS (sales)',to_char(partition_id,'yyyymmdd'),partition_id,partition_id+1);
END;
$$ LANGUAGE 'plpgsql';

As you see this function takes date as parameter. This date will be used for partition names and check constraints. This function is quite simple to write if anyone needs explanation refer to postgresql documentation or ask here. Now we need a trigger function which will insert data to appropriate partitions and if partition not found must call the function create_daily_partition_tables
-- trigger function
CREATE OR REPLACE FUNCTION sales_insert_trigger()
RETURNS TRIGGER AS $$
DECLARE
flag boolean;
tmp_partition text;
BEGIN
    --check if the partition exist if not create partition
    tmp_partition:='sales_'||to_char(NEW.created_at,'yyyymmdd');
    SELECT CASE WHEN COUNT(*) > 0 THEN TRUE ELSE FALSE END INTO flag FROM pg_class WHERE relname::text=tmp_partition;
    IF (flag) THEN

        EXECUTE 'INSERT INTO ' || tmp_partition
        ||' (amount,market_id,created_at) '
        || ' VALUES (' || NEW.amount
        ||','||NEW.market_id||','
        ||quote_literal(NEW.created_at) ||')' ;

    ELSE
        EXECUTE create_daily_partition_tables(NEW.created_at);
        --we should also send NEW record to partition

        EXECUTE 'INSERT INTO ' || tmp_partition
        ||' (amount,market_id,created_at) '
        || ' VALUES (' || NEW.amount
        ||','||NEW.market_id||','
        ||quote_literal(NEW.created_at) ||')' ;


    END IF;
    RETURN NULL;
END;
$$
LANGUAGE plpgsql;

Ok. After creating that function we should add trigger.
    CREATE TRIGGER insert_sales_trigger
    BEFORE INSERT ON measurement
    FOR EACH ROW EXECUTE PROCEDURE sales_insert_trigger();

Lets insert some data and look at output.
select createDailyDataForPartition();
and thats all.. lets check partitions and EXPLAIN ANALYZE output.

select count(*) from sales;
















and partitions...


postgresql version 9.1.3








15 Mart 2012 Perşembe

postgresql 9.0 to 9.1 upgrade

Today, when i try to upgrade postgreSQL in our production servers i got some difficulties so i briefly explain the all scenario here.

first of all stop postgresql  
sudo /etc/postgresql9-0 stop

get latest RPM's from postgresSQL repos
i use wget to take rpm then

rpm -ivh your_postgresql_rpm.rpm
yum install postgresql91.x86_64 postgresql91-contrib.x86_64  postgresql91-devel.x86_64 postgresql91-libs.x86_64 postgresql91-server.x86_64

after installation complete initialize new cluster (do not start just initdb)
sudo /etc/init.d/postgresql9.1 initdb

ok now, there is a problem in ldconfig's lets change them (details here

cd /etc/ld.so.conf.d
mv postgresql-9.0-libs.conf postgresql-9.old-libs.conf
ldconfig

now lets start to play with pg_upgrade
pg_upgrade has great syntax

/usr/pgsql-9.1/bin/pg_upgrade -d /var/lib/pgsql/9.0/data/ -D /var/lib/pgsql/9.1/data/ -b /usr/pgsql-9.0/bin/ -B /usr/pgsql-9.1/bin/ -v

-d old data dir
-D new data dir
-b old binary dir
-B new binary dir

and after installation complete. it's done :)

22 Aralık 2011 Perşembe

Postgresql SSL Kurulumu

postgresql bağlantı ayarları kısmında SSL özelliğinin var olduğunu göreceksiniz. Şimdi burda bu SSL'i neden ve nasıl kullanacağımızı bilmek mühim. Ben burda basit olarak  sorgularınızın network üzerinde (client dan postgresql server'ınıza) şifreleyerek nasıl göndereceğinizden bahsedeceğim.

ilk olarak SSL kullanmadığımız bir bağlantıyı izleyip neler olup bittiğini görelim. ben network'u izlemek için tcpdump kullanacağım siz istediğiniz bir yazılımla ortamı dinleyebilirsiniz.

tcpdump -i eth0 -X -s 3000 host 10.22.22.76
malumunuz burdaki 10.22.22.76 no'lu adres, gelen paketleri izleyeceğimiz veritabanı server makinasının adresi.

daha sonra neler olup bittiğini görmek için aynı makinaya psql ile local makinamdan bağlanıp, çeşitli sorgular yollayacağım. bu arada gözünüz bir yandan da tcpdump çıktısında olsun.

psql -U postgres -d test -h 10.22.22.76
-- test için bir tablo yaratalım 
create table foo (id integer,password varchar(32));
-- şimdi bir satır girelim ve tcpdump ile bu girdiğimiz veriyi izleyelim
insert into foo values (1,'en gizlisinden password');

eğer girdiğimiz datayı tcpdump ile takip ettiysek şöyle bir çıktı görmeniz gerekiyor.


farkettiyseniz orada bir yerlerde bizim gönderdiğimiz sorgu ve dönüşünde postgres'in bize verdiği
yanıtı görebilirsiniz. Bunu biraz düşünün neler olabilir. Eğer tehlikeli olabileceğini düşünüyorsanız okumaya devam.

peki gelelim SSL kurulumuna.

ilk olarak bir SSL sertifikası yaratmamız gerekiyor. sırasıyla aşağıdakileri uygularsak eğer tamamdır,

#openSSL paketlerinin kurulu olduğunu varsayıyorum tabi ki.
openssl genrsa -des3 -out server.key 1024
#CRT dosyasını çıkaralım
openssl rsa -in server.key -out server.key
openssl req -new -key server.key -x509 -out server.crt
# istediğiniz encryption algoritmasını kullanabilirsiniz. (man openssl) 

bunları yaptıktan sonra yarattığımız bu iki dosyayı (server.key,server.crt)
$PGDATA klasörünün altına taşımamız gerekiyor.

Daha sonra $PGDATA/postgresql.conf içinde SSL = off kısmını on olarak değiştirip
server a restart attığımızda aynı sorguları tekrar denersek

burda mutlaka ama mutlaka atlamamız gereken bir kısım var, yarattığımız dosyaların
sahibi (owner) mutlaka postgres olmalı ve izinler 0600 olaral verilmeli.
chown postgres server.*
chmod 0600 server.*

psql -U postgres -d test -h 10.22.22.76
-- şimdi bir satır girelim ve tcpdump ile bu girdiğimiz veriyi izleyelim
insert into foo values (1,'en gizlisinden password');

tcpdump çıktısında şöyle şeyler göreceğiz.


gördüğümüz gibi artık bağlantılarımızda dışardan birisi dinlediğinde anlamsız veriler görecektir. tabi bu çok basit bir çözüm ( en ufak yapılması gereken diyelim) bunun dışında her bir client ile de sertifika paylaşımı sağlamak gibi daha iyi çözümler var. bunlarıda daha sonra anlatacağım.

16 Kasım 2011 Çarşamba

WARNING: nonstandard use of escape in a string literal

This is just a warning. You can either ignore or change the code that sends SQL queries to use standart-string literal.

to remove it from log file open postgresql.conf and edit line :
escape_string_warning = on
change it to 
escape_string_warning = off

or you can just change log behavior

log_min_messages = warning                # values in order of decreasing detail:change it to 
log_min_messages = error

19 Eylül 2011 Pazartesi

PostgreSQL 9.0 Ubuntu kurulum

geçenlerde gene biri sormuştu apt ile nasıl yapacaz edecez diye.


sudo add-apt-repository ppa:pitti/postgresql


repo yu ekledikten sonra bir update çakalım

sudo apt-get update


akabinde yüklememizi yapalım

sudo apt-get install postgresql-9.0


bitti.

Ubuntu ethernet - UNCLAIMED error (intel)

first check your ethernet card

lspci -nnk | grep Eth


you should see something like

00:19.0 Ethernet controller [0200]: Intel Corporation Device [8086:1502] (rev 04)


now get the e100e driver from here

then :

tar -xf e1000e-1.2.20.tar.gz
cd e1000e-1.2.20/src
make install
modprobe e1000e


thats all.