이번 포스팅에서는 Greenplum DB에서 PL/R을 실행했을 때 "Error on receive from seg0 slice1: server closed the connection unexpectedly" 에러가 발생했을 경우 확인해보고 수정하는 예를 소개하겠습니다. 


먼저, PL/R의 input으로 사용할 array 가 들어있는 간단한 예제 테이블을 만들어보겠습니다. 



create schema test;


drop table if exists test.sample_tbl_agg;

create table test.sample_tbl_agg (

grp varchar(50) not null

, prod varchar(50)

, x_agg integer[]

, y_agg numeric[]

) distributed by (grp, prod);


insert into test.sample_tbl_agg values ('AA', 'GGG', array[1, 2, 3, 4, 5], array[0.112, 0.243, 0.318, 0.416, 0.534]);

insert into test.sample_tbl_agg values ('BB', 'SSS', array[1, 2, 3, 4, 5], array[0.103, 0.212, 0.302, 0.453, 0.593]);


select * from test.sample_tbl_agg;





 (1) Greenplum DB에서 PL/R return 받는 데이터 유형을 잘못 설정할 경우 

     server closed the connection unexpectedly 에러 발생


예제로 사용한 PL/R 함수는 국소 회귀모형(Local Regression)R의 LOESS() 함수를 적합해서 평활(smoothing)한 결과값을 array형태로 반환하는 것입니다. 그런데 returns numeric[] 으로 하는 바람에 서버가 비정상적으로 끊기고 리커버리 되는 상황이 발생하였습니다. 



------------------------------------------

-- PL/R on Greenplum : Error

--Query execution failed

--

--Reason:

--SQL Error [58M01]: ERROR: Error on receive from seg0 slice1 172.17.0.2:40000 pid=895: server closed the connection unexpectedly

--  Detail: 

-- This probably means the server terminated abnormally

-- before or while processing the request.

------------------------------------------


drop function if exists test.exec_loess(integer[], numeric[]);

create or replace function test.exec_loess(x integer[], y numeric[]) 

returns numeric[] -- **** it causes an error ****

as 

$$

y_loess <- loess(y~ x)$fitted 

return (y_loess)

$$ language plr;


-- Execute PL/R LOESS, but Error (server closed -_-;;;)

select 

grp

, prod

, x_agg

, test.exec_loess(x_agg, y_agg) as y_loess -- error

from test.sample_tbl_agg;





  (2) PL/R return 데이터 유형을 맞게 설정해줄 경우 정상 수행됨


이번에는 위의 (1)번에서 정의했던 PL/R 함수를 삭제(drop)하고 y 에 해당하는 인자의 데이터 유형을 기존numeric[] 에서 float8[] 으로 새로 변경하고, return 받는 데이터 유형도 기존의  returns numeric[] 에서 returns float8[] 로 변경하여 정의한 후에 PL/R 함수를 실행해보겠습니다. PL/R이 Greenplum에서 정상적으로 잘 수행되네요. 


PL/R 수행 후의 결과를 return 받을 데이터 유형 (data type)을 잘 확인하고 맞게 설정을 해주어야 합니다. 



-- drop old PL/R UDF which used 'returns numeric[]'

drop function if exists test.exec_loess(integer[], numeric[]); 


-- create new PL/R UDF which uses 'returns float[]'

drop function if exists test.exec_loess(integer[], float8[]);

create or replace function test.exec_loess(x integer[], y float8[]) 

returns float8[] -- ** not numeric[], but float8[] **

as 

$$

y_loess <- loess(y~ x)$fitted 

return (y_loess)

$$ language plr;



-- Execute PL/R LOESS

select 

grp

, prod

, x_agg

, test.exec_loess(x_agg, y_agg) as y_loess_agg

from test.sample_tbl_agg;





본 포스팅의 주제는 아닙니다만, 위의 PL/R에서 반환받는 값이 array 형태이다보니 조회해서 보기에, 또 이를 가지고 연산을 하거나 다른 테이블과 조인을 하기에 불편한감이 있습니다. 이에 unnest() 함수를 사용해서 array를 풀어서 long format으로 조회하는 예제도 추가로 아래에 예를 들어보았습니다. 



-- unnest to see in a long format

select 

a.grp

, a.prod

, unnest(a.x_agg) as x

, unnest(a.y_loess_agg) as y_loess

from (

select 

grp

, prod

, x_agg

, test.exec_loess(x_agg, y_agg) as y_loess_agg

from test.sample_tbl_agg) a;






  (3) returns setof 을 사용해서 행(row) 단위로 PL/R 결과를 반환하기

      (returns rows instead of array using 'returns setof' in PL/R on Greenplum)


위의 (2)번에서 unnest() 를 소개한 김에 옆으로 조금만 더 세어보자면요, 

위의 (2)번에서는 returns float8[]        --> PL/R 수행 시 array 를 반환했던 반면에, 

이번 (3)번에서는 returns setof float8   --> PL/R 수행 시 rows 를 반환하도록 하는 경우입니다. 


'returns setof' 처럼 'setof'가 추가된게 다르구요, 'float8[]' array가 아니라 'float8' 형태인게 다릅니다. 

이렇게 개별 float8 값을 row로 반환하므로 위의 (2)번처럼 unnest() 함수를 써서 array를 long format으로 풀어줄 필요가 없이도 (2)번의 unnest() 를 쓴 것과 동일한 결과를 얻었습니다.  



---------------------------------------

-- returns setof: rows in a long format

---------------------------------------

drop function if exists test.exec_loess(integer[], float8[]);

create or replace function test.exec_loess(x integer[], y float8[]) 

returns setof float8 -- not array, but rows

as 

$$

y_loess <- loess(y~ x)$fitted 

return (y_loess)

$$ language plr;



-- Execute PL/R LOESS

select 

grp

, prod

, unnest(x_agg) as x

, test.exec_loess(x_agg, y_agg) as y_loess -- not array, but rows

from test.sample_tbl_agg;

 




많은 도움이 되었기를 바랍니다. 

이번 포스팅이 도움이 되었다면 아래의 '공감~'를 꾹 눌러주세요. 



Posted by R Friend R_Friend

댓글을 달아 주세요

만약 한개 당 1분 걸리는 동일한 프로세스의 100개의 일을 한 명이서 한다면 100분이 걸릴텐데요, 이것을 100명에게 일을 1개씩 나누어서 동시에 분산해서 시킨다면 1분(+취합하는 시간 약간) 밖에 안걸릴 것입니다. 1명이 100명을 이길 수는 없기 때문입니다. 


대용량 데이터에 대해서 빠른 성능으로 통계나 기계학습의 고급 분석을 처리해야 하는 경우라면 Greenplum 과 같은 MPP (Massively Parallel Processing) 아키텍처 기반의 DB에서 R 이나 Python 언어로 작성한 알고리즘을 In-DB에서 PL/R, PL/Python을 사용해서 분산 병렬 처리할 수 있습니다. 


이번 포스팅에서는 Greenplum DB에서 PL/R (Procedural Language R) 을 사용해서 분산 병렬처리(distributed parallel processing하여 그룹별로 선형회귀모형을 각각 적합하고 예측하는 방법을 소개하겠습니다. 모든 연산이 In-DB 에서 일어나기 때문에 데이터 I/O 가 없으므로 I/O 시간을 절약하고 architecture 와  workflow를 간단하게 가져갈 수 있는 장점도 있습니다. (vs. DB 에서 local R 로 데이터 말아서 내리고, local R로 모형 적합 / 예측 후, 이 결과를 다시 DB에 insert 하고 하는 복잡한 절차가 필요 없음)



이번에 소개할 간단한 예제로 사용할 데이터셋은 abalone 공개 데이터셋으로서, 성 (sex) 별로 구분하여 무게(shucked_weight)와 지름(diameter) 설명변수를 input으로 하여 껍질의 고리 개수(rings)를 추정하는 선형회귀모형을 적합하고, 예측하는 문제입니다. 


이러한 일을 성별 F, M, I 별로 순차적으로 하는 것이 아니라, Greenplum DB 에서 성별 F, M, I 별로 PL/R로 분산 병렬처리하여 동시에 수행하는 방법입니다. 



  (1) abalone 데이터셋으로 테이블 만들기


먼저, abalone 데이터셋을 공개 데이터셋 웹사이트에서 가져와서 External table을 만들고, 이로부터 abalone table 을 생성해보겠습니다. 



---------------------------------

-- Linear Regression in Parallel 

-- using PL/R

---------------------------------


-- Dataset for example: abalone dataset from the UC Irvine Machine Learning Repository

-- url: http://archive.ics.uci.edu/ml/machine-learning-databases/abalone/abalone.data

-- Create an external web table

DROP EXTERNAL TABLE IF EXISTS abalone_external;

CREATE EXTERNAL WEB TABLE abalone_external(

sex text 

, length float8

, diameter float8

, height float8

, whole_weight float8

, shucked_weight float8

, viscera_weight float8

, shell_weight float8

, rings integer -- target variable to predict

) LOCATION('http://archive.ics.uci.edu/ml/machine-learning-databases/abalone/abalone.data') 

FORMAT 'CSV' 

(null as '?');



-- Create a table of abalone

DROP TABLE IF EXISTS abalone;

CREATE TABLE abalone AS 

SELECT * FROM abalone_external

DISTRIBUTED BY (sex);



-- Viewing data distribution

SELECT gp_segment_id, COUNT(*) AS row_cnt

FROM abalone

GROUP BY gp_segment_id;




-- Check data

SELECT * FROM abalone LIMIT 5;






  (2) Train, Test set 테이블 분할 (train, test set split)


다음으로 MADlib(https://madlib.apache.org/) 의 madlib.train_test_split() 함수를 사용해서 abalone 원 테이블을 train set, test set 테이블로 분할(split into train and test set) 해보겠습니다. 



---------------------------

-- Train, Test set split

---------------------------

-- Check the number of observations per sex group(F, I, M)
SELECT
 sex, COUNT(*) FROM abalone GROUP BY sex;




-- Train, Test set split

DROP TABLE IF EXISTS out_train, out_test;

SELECT madlib.train_test_split(

'abalone',    -- Source table

'out',     -- Output table

    0.8,       -- train_proportion

    NULL,      -- Default = 1 - train_proportion = 0.5

    'sex', -- Strata definition

    'rings, shucked_weight, diameter', -- Columns to output

    FALSE,      -- Sample with replacement

    TRUE);     -- Separate output tables



SELECT * FROM out_train LIMIT 5;




SELECT sex, count(*) FROM out_train GROUP BY sex;




SELECT sex, count(*) FROM out_test GROUP BY sex;






  (3) array aggregation 하여 PL/R에서 사용할 데이터셋 준비하기


좀 낯설을 수도 있는데요, PL/R 에서는 array 를 input으로 받으므로 array_agg() 함수를 사용해서 설명변수를 칼럼별로 array aggregation 해줍니다. 이때 성별(sex) 로 모형을 각각 병렬로 적합할 것이므로 group by sex 로 해서 성별로 따로 따로 array aggregation 을 해줍니다. 이렇게 해주면 long format으로 여러개의 열(row)에 들어있던 값들이 성별로 구분이 되어서 하나의 array( { } )에 모두 들어가게 됩니다. (아래 이미지 참조)


 

-- Data Preparation

-- : array aggregation using array_agg()

DROP TABLE IF EXISTS abalone_array;

CREATE TABLE abalone_array AS 

SELECT

sex::text -- group

, array_agg(rings::float8) as rings             -- y

, array_agg(shucked_weight::float8) as s_weight -- x1

, array_agg(diameter::float8) as diameter       -- x2

FROM out_train

GROUP BY sex

DISTRIBUTED BY (sex);


SELECT * FROM abalone_array;



-- : 2D array aggregation using MADlib.matrix_agg()

CREATE TABLE mat_agg AS (

    SELECT 

sex

, madlib.matrix_agg(array[rings, shucked_weight, diameter]) AS mat_agg

    FROM abalone

    GROUP BY sex

) DISTRIBUTED BY (sex);


SELECT * FROM mat_agg;





  (4) 선형회귀모형 적합하는 PL/R 사용자 정의 함수 정의하기 (Define PL/R UDF)


선형회귀모형 PL/R 의 반환받는 값을 두가지 유형으로 나누어서 각각 소개하겠습니다.


(4-1) 적합된 회귀모형의 회귀계수 (coefficients) 를 반환하기

(4-2) 적합된 회귀모형 자체(fitted model itself)를 반환하기 



먼저, (4-1) 적합된 회귀모형의 회귀계수를 반환하는 PL/R 함수를 정의하는 방법을 소개하겠습니다. 


R의 lm() 함수를 사용하여 다중 선형회귀모형을 적합(fit a model)하면, summary(fitted_model)$coef 는 추정된 회귀계수(coef_est), 표준오차(std_error), T통계량(t_stat), P-값 (p_value) 를 반환합니다. 


CREATE OR REPLAE FUNCTION pl_r_funtion_name(column_name data_type[], ...) 으로 PL/R 함수 이름과 인자로 받는 칼럼 이름, 데이터 유형을 정의해주고, 


이들 모형 적합 후의 추정된 회귀계수와 통계량을 Greenplum DB에 반환하려면 데이터 유형이 여러개이므로 composit type 을 별도로 정의('lm_abalone_type')해주어고, PL/R 사용자 정의함수에서 returns setof lm_abalone_type 처럼 써주면 됩니다.


그 다음에, $$ pure R codes block $$ LANGUAGE 'plr' 형식으로 R codes 를 통째로 $$ ~~~~ $$ 안에 넣어주면 됩니다. 



----------------------------------------------------------------

-- (4-1) PL/R : Linear Regression Model's Coefficients --> Predict

----------------------------------------------------------------


-- Return Types

DROP TYPE IF EXISTS lm_abalone_type CASCADE;

CREATE TYPE lm_abalone_type AS (

variable text

, coef_est float

, std_error float

, t_stat float

, p_value float

);


-- PL/R User Defined Function

DROP FUNCTION IF EXISTS plr_lm_train(float8[], float8[], float8[]);

CREATE OR REPLACE FUNCTION plr_lm_train(

rings float8[]

, s_weight float8[]

, diameter float8[]

) RETURNS SETOF lm_abalone_type AS

$$

m1 <- lm(rings ~ s_weight + diameter)

m1_s <- summary(m1)$coef

temp_m1 <- data.frame(rownames(m1_s), m1_s)

return(temp_m1)

$$

LANGUAGE 'plr';

 





 (5) PL/R 실행하기 (execute PL/R UDF in Greenplum DB)


위의 (4)에서 정의한 성별(per sex) 선형회귀모형을 적합하여 회귀계수와 통계량을 반환하는 PL/R 사용자 정의함수를 Greenplum DB 안에서 병렬처리하여 실행해보겠습니다. 


select sex, (plr_lm_train(rings, s_weight, diameter)).* from abalone_array 처럼 위의 (4)번에서 정의한 PL/R 함수에 (3)번에서 준비한 array 가 들어있는 테이블의 칼럼을 써주고, from 절에 array 테이블 이름을 써주면 됩니다. 


이때 테이블에 return 받는 값들이 composit type의 여러개 칼럼들이므로 (plr_udf(column, ...)).* 처럼 PL/R 함수를 괄호 ( ) 로 싸주고 끝에 '*' (asterisk) 를 붙여줍니다. ( * 를 빼먹으면 여러개의 칼럼별로 나누어지지 않고 한개의 칼럼에 튜플로 모든 칼럼이 뭉쳐서 들어갑니다)



-- Execution of Linear Regression PL/R 

DROP TABLE IF EXISTS lm_abalone_model_coef;

CREATE TABLE lm_abalone_model_coef AS (

SELECT sex, (plr_lm_train(rings, s_weight, diameter)).* 

FROM abalone_array

) DISTRIBUTED BY (sex);


SELECT * FROM lm_abalone_model_coef;







  (6) 적합한 선형회귀모형을 사용해 test set에 대해 예측하기 (prediction on test set)


위의 (5)번에서 적합한 성별 선형회귀모형들의 회귀계수 (coefficients per sex groups) 를 사용해서 test set 의 데이터셋에 대해 PL/R 함수로 분산 병렬처리하여 rings 값을 예측해보겠습니다. (training 도 분산병렬처리, prediction/ scoring 도 역시 분산병렬처리!)


먼저, 예측하는 PL/R 함수에 넣어줄 test set을 array aggregation 해줍니다. 


다음으로, ID별로 실제값(actual rings)과 예측한 값(predicted rings)을 반환받을 수 있도록 composite type 을 정의해줍니다. 


그 다음엔 추정된 회귀계수를 사용해서 예측할 수 있도록 행렬 곱 연산을 하는 PL/R 함수(plr_lm_coef_predict())를 정의해줍니다. 


마지막으로 예측하는 PL/R 함수를 실행해줍니다. 



------------------------------------------------

-- Prediction and Model Evaluation for Test set

------------------------------------------------


-- Preparation of test set in aggregated array

DROP TABLE IF EXISTS test_array;

CREATE TABLE test_array AS 

SELECT

sex::text

, array_agg(rings::float8) as rings             -- y

, array_agg(shucked_weight::float8) as s_weight -- x1

, array_agg(diameter::float8) as diameter       --x2

FROM out_test

GROUP BY sex

DISTRIBUTED BY (sex);


SELECT * FROM test_array;




-- Define composite data type for predicted return values

DROP TYPE IF EXISTS lm_predicted_type CASCADE;

CREATE TYPE lm_predicted_type AS (

id int

, actual float

, pred float

);



-- Define PL/R UDF of prediction per groups using linear regression coefficients

DROP FUNCTION IF EXISTS plr_lm_coef_predict(float8[], float8[], float8[], float8[]);

CREATE OR REPLACE FUNCTION plr_lm_coef_predict(

rings float8[]

, s_weight float8[]

, diameter float8[]

, coef_est float8[]

) RETURNS SETOF lm_predicted_type AS

$$

actual <- rings # y

intercept <- 1

X <- cbind(intercept, s_weight, diameter) # X matrix

coef_est <- matrix(coef_est) # coefficients matrix

predicted <- X %*% coef_est  # matrix multiplication

df_actual_pred <- data.frame(actual, predicted)

id <- as.numeric(rownames(df_actual_pred))

return(data.frame(id, df_actual_pred))

$$

LANGUAGE 'plr';



-- Execute PL/R Prediction UDF

DROP TABLE IF EXISTS out_coef_predict;

CREATE TABLE out_coef_predict AS (

SELECT sex, (plr_lm_coef_predict(c.rings, c.s_weight, c.diameter, c.coef_est)).*

FROM (

SELECT a.*, b.coef_est

FROM test_array a, 

(SELECT sex, array_agg(coef_est) AS coef_est FROM lm_abalone_model_coef GROUP BY sex) b

WHERE a.sex = b.sex

) c

) DISTRIBUTED BY (sex);



-- Compare 'actual' vs. 'predicted'

SELECT * FROM out_coef_predict WHERE sex = 'F' ORDER BY sex, id LIMIT 10;







  (7) 회귀모형 자체를 Serialize 해서 DB에 저장하고, Unserialize 해서 예측하기


위의 (4)번~(6번) 까지는 적합된 회귀모형의 회귀계수와 통계량을 반환하고, 이를 이용해 예측을 해보았다면, 이번에는 


- (4-2) 적합된 회귀모형 자체(model itself)를 Serialize 하여 DB에 저장하고 (인코딩)

- 이를 DB에서 읽어와서 Unserialize 한 후 (디코딩), 예측하기

- 단, 이때 예측값의 95% 신뢰구간 (95% confidence interval) 도 같이 반환하기


를 해보겠습니다. 



--------------------------------------------------------------------

-- (2) PL/R : Linear Model --> Serialize --> Deserialize --> Predict

--------------------------------------------------------------------


-- PL/R User Defined Function Definition

DROP FUNCTION IF EXISTS plr_lm_model(float8[], float8[], float8[]);

CREATE OR REPLACE FUNCTION plr_lm_model(

    rings float8[]

, s_weight float8[]

, diameter float8[]

) RETURNS bytea -- serialized model as a byte array

AS

$$

lr_model <- lm(rings ~ s_weight + diameter)

return (serialize(lr_model, NULL))

$$

LANGUAGE 'plr';


-- Execution of Linear Regression PL/R 

DROP TABLE IF EXISTS lm_abalone_model;

CREATE TABLE lm_abalone_model AS (

SELECT sex, plr_lm_model(rings, s_weight, diameter) AS serialized_model

FROM abalone_array

) DISTRIBUTED BY (sex);



-- We can not read serialized model

SELECT * FROM lm_abalone_model;





DROP TYPE IF EXISTS lm_predicted_interval_type CASCADE;

CREATE TYPE lm_predicted_interval_type AS (

id int

, actual float

, pred float

, lwr float

, upr float

);


-- PL/R function to read a serialized PL/R model

DROP FUNCTION IF EXISTS plr_lm_model_predict(float8[], float8[], float8[], bytea);

CREATE OR REPLACE FUNCTION plr_lm_model_predict(

rings float8[]

, s_weight float8[]

, diameter float8[]

, serialized_model bytea

) RETURNS SETOF lm_predicted_interval_type 

AS

$$

model <- unserialize(serialized_model)

actual <- rings # y

X <- data.frame(s_weight, diameter) # new data X

predicted <- predict(model, newdata = X, interval = "confidence")

df_actual_pred <- data.frame(actual, predicted)

id <- as.numeric(rownames(df_actual_pred))

return (data.frame(id, df_actual_pred))

$$

LANGUAGE 'plr';



-- Predict

DROP TABLE IF EXISTS out_model_predict;

CREATE TABLE out_model_predict AS (

SELECT sex, (plr_lm_model_predict(c.rings, c.s_weight, c.diameter, c.serialized_model)).*

FROM (

SELECT a.*, b.serialized_model

FROM test_array a, lm_abalone_model b

WHERE a.sex = b.sex

) c

) DISTRIBUTED BY (sex);


SELECT * FROM out_model_predict WHERE sex = 'F' ORDER BY sex, id LIMIT 10;



  • PL/R Reference: https://pivotalsoftware.github.io/gp-r/


많은 도움이 되었기를 바랍니다. 

이번 포스팅이 도움이 되었다면 아래의 '공감~'를 꾹 눌러주세요. 



Posted by R Friend R_Friend

댓글을 달아 주세요

이번 포스팅에서는 PostgreSQL, Greenplum Database를 이용해서 시계열 데이터를 특정 시간 단위 구간(예: 10분 단위, 1시간 단위, 1일 단위, 1달 단위, 1년 단위 등) 별로 집계/ 요약하는 방법을 소개하겠습니다. 


(* python pandas로 특정 시간 단위 구간별로 시계열 데이터를 집계하는 방법은 https://rfriend.tistory.com/495 참조)


-- 사전 준비 작업 --

(1) '년-월-일' date dimension table 만들기

(2) '시간:분' time dimension table 만들기

(3) '년-월-일 시간:분' cross-join 한  date & time dimension table 만들기

(4) 예제 시계열 데이터(time series) table 만들기

    ==> '날짜-시간' 기준정보를 '시계열 거래 데이터'에 merge 하기


-- 10분 단위 구간별로 시계열 데이터 집계/ 요약하기 --

(5) 10분 단위 구간별로 첫번째 행 값(first row value), 마지막 행 값(last row value) 구하기

(6) 10분 단위 구간별로 합(sum), 누적합(cumulative sum) 구하기 

(7) 10분 단위 구간별로 최소값(min), 1사분위수(1st quantile), 중위수(median), 3사분위수(3rd quantile), 최대값(max), 범위(range) 구하기

(8) 10분 단위 구간별로 평균(mean), 표본 분산(sample variance), 표본 표준편차(sample standard deviation) 구하기

(9) 10분 단위 구간별로 수량 가중 평균 가격 구하기 (amount-weighted average of price)



  (1) '년-월-일' date dimension table 만들기


날짜(년-월-일)를 기준으로 년(year), 분기(quarter), 월(month), 주(week), 일(day), 공휴일(holiday) 등의 다양한 관점의 group by 할 수 있는 구분자를 생성해놓은 테이블을 만들어보겠습니다. 날짜(년-월-일) 관련 기준 정보, 매핑 테이블이라고 보면 되겠습니다. 


아래 예제는 2019년 12월1일 ~ 2019년 12월 31일 까지의 31일 기간의 날짜 데이터 기준정보를 만들었습니다. 

만약 2019년 1월1일~2019년 12월 31일 까지의 365일 전체의 날짜 데이터 기준정보를 만들고 싶다면 from 절을 아래와 같이 수정해주면 됩니다. 


[ 2019년 1월 1일~2019년 12월 31일 (365일) 기간 일 별 기준정보 테이블 만드는 FROM 절 generate query ]


FROM (

SELECT '2019-01-01'::DATE + SEQUENCE.DAY AS datum

FROM generate_series(0, 364) AS SEQUENCE(DAY)

GROUP BY SEQUENCE.DAY

     ) DQ



아래의 postgresql wiki 페이지의 sql query를 참고하였습니다. 

* reference: https://wiki.postgresql.org/wiki/Date_and_Time_dimensions



------------------------

-- date dimension table

------------------------

-- create 'dim_date' table

DROP TABLE IF EXISTS dim_date;

CREATE TABLE dim_date (

date DATE NOT NULL, 

year INT NOT NULL, 

month INT NOT NULL, 

month_nm VARCHAR(9) NOT NULL, 

day INT NOT NULL, 

day_of_year INT NOT NULL, 

weekday_nm VARCHAR(9) NOT NULL, 

calender_week INT NOT NULL, 

formatted_date CHAR(12) NOT NULL, 

quartal CHAR(2) NOT NULL, 

year_quartal CHAR(7) NOT NULL, 

year_month CHAR(7) NOT NULL, 

year_calendar_week CHAR(7) NOT NULL, 

weekend VARCHAR(7) NOT NULL, 

holiday_korea VARCHAR(20)     NOT NULL, 

period VARCHAR(20)     NOT NULL, 

cwstart DATE NOT NULL, 

cwend DATE NOT NULL, 

month_start DATE NOT NULL, 

month_end DATE NOT NULL

);


-- create index on 'date'

CREATE INDEX dim_date_date_idx ON dim_date(date);


-- insert generate date dimension data (from 2019-01-01 to 2019-12031) into 'dim_date' table

INSERT INTO dim_date (

SELECT

datum AS date,

EXTRACT(YEAR FROM datum)::INT AS year,

EXTRACT(MONTH FROM datum)::INT AS month,

-- Localized month name

to_char(datum, 'TMMonth') AS month_nm,

EXTRACT(DAY FROM datum)::INT AS day,

EXTRACT(doy FROM datum)::INT AS day_of_year,

-- Localized weekday

to_char(datum, 'TMDay') AS weekday_nm,

-- ISO calendar week

EXTRACT(week FROM datum)::INT AS calendar_week,

to_char(datum, 'dd. mm. yyyy') AS formatted_date,

'Q' || to_char(datum, 'Q') AS quartal,

to_char(datum, 'yyyy/"Q"Q') AS year_quartal,

to_char(datum, 'yyyy/mm') AS year_month,

-- ISO calendar year and week

to_char(datum, 'iyyy/IW') AS year_calendar_week,

-- Weekend

CASE WHEN EXTRACT(isodow FROM datum) IN (6, 7) THEN 'Weekend' 

ELSE 'Weekday' END 

AS weekend,

-- holidays for Korea in year 2019

    CASE WHEN to_char(datum, 'MMDD') IN ('0101', '0205', '0206', '0207', '0301', '0505', '0512', 

                                         '0606', '0815', '0912', '0913', '0914', '1003', '1009', '1225')

THEN 'Holiday' ELSE 'No holiday' END

AS holiday_korea,

-- Some periods of the year, adjust for your organisation and country

CASE WHEN to_char(datum, 'MMDD') BETWEEN '0701' AND '0831' THEN 'Summer break'

    WHEN to_char(datum, 'MMDD') BETWEEN '1115' AND '1225' THEN 'Christmas season'

    WHEN to_char(datum, 'MMDD') > '1225' OR to_char(datum, 'MMDD') <= '0106' THEN 'Winter break'

ELSE 'Normal' END

AS period,

-- ISO start and end of the week of this date : from monday to sunday

datum + (1 - EXTRACT(isodow FROM datum))::INTEGER AS CWStart, -- monday

datum + (7 - EXTRACT(isodow FROM datum))::INTEGER AS CWEnd,   -- sunday

-- Start and end of the month of this date

datum + (1 - EXTRACT(DAY FROM datum))::INTEGER AS month_start,

((datum + (1 - EXTRACT(DAY FROM datum))::INTEGER + '1 month'::INTERVAL)::DATE - '1 day'::INTERVAL)::DATE AS month_end

FROM (

SELECT '2019-12-01'::DATE + SEQUENCE.DAY AS datum

FROM generate_series(0, 30) AS SEQUENCE(DAY)

GROUP BY SEQUENCE.DAY

     ) DQ

ORDER BY 1);



SELECT * FROM dim_date ORDER BY date;






  (2) '시간:분' time dimension table 만들기


시간('시간:분')을 기준으로 시간(time), 분(minute), 10분 단위 구간(time_span_min_10), 1시간 단위 구간(time_span_hour_1), 일 구분 범주(day_category) 등으로 구분해서 집계/요약할 수 있는 구분자, 기준 정보, 매핑 테이블을 만들어보겠습니다. 


* reference: https://wiki.postgresql.org/wiki/Date_and_Time_dimensions



------------------------

-- time dimension table

------------------------

-- create dimension table 'dim_time'

DROP TABLE IF EXISTS dim_time;

CREATE TABLE dim_time (

time_of_day CHAR(6) NOT NULL,

hour INT NOT NULL, 

minute INT NOT NULL,

time_span_min_10 CHAR(15) NOT NULL, 

time_span_hour_1 CHAR(15) NOT NULL, 

day_category VARCHAR(15) NOT NULL

);


-- create index on 'time_of_day'

CREATE INDEX dim_time_time_of_day_idx ON dim_time(time_of_day);


-- insert generated time_of_day data into 'dim_time' table

INSERT INTO dim_time (

SELECT 

TO_CHAR(MINUTE, 'hh24:mi') AS time_of_day,

-- Hour of the day (0 - 23)

EXTRACT(HOUR FROM MINUTE)::INTEGER AS hour, 

-- Minute of the day (0 - 1439)

(EXTRACT(HOUR FROM MINUTE)*60 + EXTRACT(MINUTE FROM MINUTE))::INT AS minute,

-- Extract and format 10 minutes time span

TO_CHAR(MINUTE - (EXTRACT(MINUTE FROM MINUTE)::INTEGER % 10 || 'minutes')::INTERVAL, 'hh24:mi') ||

' – ' ||

TO_CHAR(MINUTE - (EXTRACT(MINUTE FROM MINUTE)::INTEGER % 10 || 'minutes')::INTERVAL + '10 minutes'::INTERVAL, 'hh24:mi')

AS time_span_min_10, 

-- Extract and format 1 hour time span

TO_CHAR(MINUTE - (EXTRACT(MINUTE FROM MINUTE)::INTEGER % 60 || 'minutes')::INTERVAL, 'hh24:mi') ||

' – ' ||

TO_CHAR(MINUTE - (EXTRACT(MINUTE FROM MINUTE)::INTEGER % 60 || 'minutes')::INTERVAL + '60 minutes'::INTERVAL, 'hh24:mi')

AS time_span_hour_1, 

-- Names of day periods

CASE WHEN to_char(MINUTE, 'hh24:mi') BETWEEN '06:00' AND '08:59' THEN 'morning'

    WHEN to_char(MINUTE, 'hh24:mi') BETWEEN '09:00' AND '11:59' THEN 'AM'

    WHEN to_char(MINUTE, 'hh24:mi') BETWEEN '12:00' AND '17:59' THEN 'PM'

    WHEN to_char(MINUTE, 'hh24:mi') BETWEEN '18:00' AND '21:59' THEN 'evening'

    ELSE 'night'

END AS day_category

FROM (SELECT '00:00'::TIME + (SEQUENCE.MINUTE || ' minutes')::INTERVAL AS MINUTE

FROM generate_series(0,1439) AS SEQUENCE(MINUTE)

GROUP BY SEQUENCE.MINUTE

     ) DQ

ORDER BY 1

);


SELECT * FROM dim_time ORDER BY time_of_day LIMIT 60;

 

 



  (3) '년-월-일 시간:분' cross-join 한  date & time dimension table 만들기


(1)번에서 만든 dim_date 테이블과 (2)번에서 만든 dim_time 테이블을 cross join 하여 'dim_date_time' 테이블을 만들어보겠습니다. 


31일 (dim_date) * 24시간 * 60분 (dim_time) = 44,640 개의 전체 행 (total number of rows) 을 가지는 '년-월-일 시간:분' (dim_date_time) 기준정보 매핑 테이블을 가지고 이제 년 ~ 분 단위를 넘나들면서 원하는 시간 단위 구간별로 시계열 데이터를 집계/ 요약할 수 있습니다


앞으로 join 의 기준이 될 '년-월-일 시간:분'(dt) 칼럼에 대해서는 빠른 join을 위해서 index 를 생성해주었습니다. 



---------------------------------------

-- cross join b/w dim_date and dim_time

---------------------------------------

DROP TABLE IF EXISTS dim_date_time;

CREATE TABLE dim_date_time AS (

SELECT 

to_char((dim_date.date || ' ' || dim_time.time_of_day)::timestamp, 'yyyy-mm-dd hh24:mi') AS dt

, dim_date.*

, dim_time.*

FROM dim_date

CROSS JOIN dim_time

);


-- create index on 'dt'

CREATE INDEX dim_date_time_dt_idx ON dim_date_time(dt);



SELECT * FROM dim_date_time ORDER BY dt LIMIT 5;



SELECT COUNT(*) FROM dim_date_time;

44640





  (4) 예제 시계열 데이터(time series) table 만들기 ==> '날짜-시간' 기준 정보 merge 하기


간단한 예제로 '년-월-일 시간:분'(dt), 가격(price), 수량(amt) 의 3개 칼럼을 가지는 거래 시계열 데이터 테이블을 만들어보겠습니다. 



---------------------

-- Transaction table

---------------------

-- create ts table

DROP TABLE IF EXISTS ts;

CREATE TABLE ts (

dt  TEXT NOT NULL

, price NUMERIC NOT NULL 

, amt INT NOT NULL

);


-- insert transaction data into ts table

INSERT INTO ts VALUES 

('2019-12-19 00:00', 12, 4), 

('2019-12-19 00:02', 21, 2),

('2019-12-19 00:04', 41, 1),

('2019-12-19 00:06', 79, 4),

('2019-12-19 00:08', 61, 2),

('2019-12-19 00:10', 81, 1),

('2019-12-19 00:12', 24, 3),

('2019-12-19 00:14', 62, 1),

('2019-12-19 00:16', 76, 3),

('2019-12-19 00:18', 63, 1),

('2019-12-19 00:20', 95, 2),

('2019-12-19 00:22', 82, 1),

('2019-12-19 00:24', 82, 3),

('2019-12-19 00:26', 70, 1),

('2019-12-19 00:28', 30, 4),

('2019-12-19 00:30', 33, 1),

('2019-12-19 00:32', 22, 2),

('2019-12-19 00:34', 77, 3),

('2019-12-19 00:36', 58, 3),

('2019-12-19 00:38', 96, 3), 

('2019-12-19 09:02', 10, 2),

('2019-12-19 09:08', 50, 3),

('2019-12-19 14:04', 20, 1),

('2019-12-19 16:00', 10, 5),

('2019-12-19 21:00', 30, 4)

;


SELECT * FROM ts ORDER BY dt;





다음으로, '시계열 거래 데이터'(ts table)에 '날짜-시간 기준정보'(dim_date_time table) 를 '년-월-일 시간:분'(dt) 칼럼을 기준으로 병합(left outer join)해보겠습니다. 이제 특정 시간 단위 구간별로 집계, 요약할 수 있는 데이터 준비가 되었습니다. 


※ 만약 '시계열 거래 데이터'에 특정 날짜-시간대에 값이 존재하던지 말던지 간에 동일한 특정 시간 단위 구간별로 값을 집계, 요약하고자 한다면, (즉, '시계열 거래 데이터'가 없으면 NULL이나 '0'으로 집계) LEFT OUTER JOIN 할 때 왼쪽에 '날짜-시간 기준정보'(dim_date_time) 테이블을 놓고, 오른쪽에 '시계열 거래 데이터'(ts) 테이블을 놓고 left outer join 을 해주어야 합니다. 



------------------------------------

-- merge 'date-time' dimension table

------------------------------------

DROP TABLE IF EXISTS ts_dt;

CREATE TABLE ts_dt AS (

SELECT 

a.*

, b.date

, b.time_span_min_10

, b.time_span_hour_1

, b.day_category

FROM ts a 

LEFT OUTER JOIN dim_date_time b ON a.dt = b.dt

);


SELECT * FROM ts_dt ORDER BY dt LIMIT 5;





  (5) 10분 단위 구간별로 첫번째 행 값(first-row value), 마지막 행 값(last row value) 구하기


시계열 데이터를 시간 기준으로 정렬했을 때 10분 단위 구간(10 minutes time span)별로 첫번째 행의 값(first row's value)을 구하는 방법에는 두가지가 있습니다. 


첫번째 방법은 ROW_NUMBER() OVER (PARTITION BY date, time_span_min_10 ORDER BY dt) 의 window function을 사용하는 것입니다. (오름차순 정렬 order by dt)


두번째 방법은 DISTINCT ON (date, time_span_min_10) 함수를 사용하는 것입니다. 



-- (1) first, last by time_span_min_109

-- (1-1-1) way 1: first using ROW_NUMBER()

WITH time_span_min_10_tmp AS (

SELECT 

date

, time_span_min_10

, price

, ROW_NUMBER() OVER (PARTITION BY date, time_span_min_10 ORDER BY dt) AS row_num

FROM ts_dt

)

SELECT 

date

, time_span_min_10

, price AS price_10m_first

FROM time_span_min_10_tmp

WHERE row_num = 1

;


-- (1-1-2) way 2: first using DISTINCT ON

SELECT DISTINCT ON (date, time_span_min_10)

date

, time_span_min_10

, price AS price_10m_first

FROM ts_dt

ORDER  BY date, time_span_min_10, dt, price;


 



시간 기준으로 정렬했을 때 10분 단위 구간(10 minutes time span)별 마지막 행의 값(last row's value)을 구하는 두가지 방법은 위에서 소개한 첫번째 행의 값을 구하는 방법에서 정렬 기준을 정반대로 해서, 내림차순 정렬(ORDER BY dt DESC) 을 해주면 됩니다. 



-- (1-2-1) way 1: last using ROW_NUMBER()

WITH time_span_min_10_tmp AS (

SELECT 

date

, time_span_min_10

, price

, ROW_NUMBER() OVER (PARTITION BY date, time_span_min_10 ORDER BY dt DESC) AS row_num

FROM ts_dt

)

SELECT 

date

, time_span_min_10

, price AS price_10m_last

FROM time_span_min_10_tmp

WHERE row_num = 1

;


-- (1-2-2) way 2: last using DISTINCT ON

SELECT 

DISTINCT ON (date, time_span_min_10)

date

, time_span_min_10

    , price AS price_10m_last

FROM ts_dt

ORDER  BY date, time_span_min_10, dt DESC, price;

 




  (6) 10분 단위 구간별로 합(sum), 누적합(cumulative sum) 구하기 


10분 단위 구간(10 minutes time span) 별로 가격(price) 칼럼의 합(sum)을 먼저 구하고, ==> 시간 순서대로 정렬된 상태에서 10분 단위 구간별로 SUM() OVER () window function을 사용해서 누적 합(cumulative sum)을 구해보겠습니다. 



-- (2) sum, cumulative sum

-- sum, cumulative sum by time_span_min_10

WITH price_10m_sum_tmp AS (

SELECT 

date

, time_span_min_10

, sum(price) AS price_10m_sum

FROM ts_dt

GROUP BY 1, 2

)

SELECT 

date

, time_span_min_10

, price_10m_sum

, SUM(price_10m_sum) 

OVER ( ORDER BY time_span_min_10 

ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) 

AS price_10m_cumsum 

FROM price_10m_sum_tmp

;

 




집계/ 요약을 하는 시간 단위 구간을 위의 10분에서 이번에는 1 시간 (1 hour time span) 단위 구간별 변경해서 가격 합(sum)과 가격 누적합(cumulative sum)을 구해보겠습니다. 위의 (3)번에서 '날짜-시간 기준정보 테이블'(dim_date_time table)에서 '1시간 단위 구간' 칼럼 (time_span_hour_1) 을 만들어놓았었기 때문에 group by 집계/ 요약하는 기준이 되는 칼럼을 'time_span_min_10'을 'time_span_hour_1'으로 변경해주기만 하면 됩니다. 



-- sum, cumulative sum by time_span_hour_1

WITH price_1h_sum_tmp AS (

SELECT 

date

, time_span_hour_1

, sum(price) AS price_1h_sum

FROM ts_dt

GROUP BY 1, 2

)

SELECT 

date

, time_span_hour_1

, price_1h_sum

, SUM(price_1h_sum) 

OVER ( ORDER BY time_span_hour_1

ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW)

AS price_1h_cumsum

FROM price_1h_sum_tmp;





  (7) 10분 단위 구간별로 최소값(min), 1사분위수(1st quantile), 중앙값(median), 

       3사분위수(3rd quantile), 최대값(max), 범위(range) 구하기


최소값은 min(), 최대값은 max(), 1사분위수/중앙값/3사분위수는 percentile_disc(), 범위는 max() - min() 함수를 이용해서 time_span_min_10 으로 group by 집계, 요약하였습니다. 


1/2/3 사분위수(quantile) 구하는 percentile_disc(0.25 / 0.5 / 0.75) within group (order by price) 함수를 사용해서 IQR ( = q3 - q1) 구할 수도 있습니다. 



-- (3) min, q1, median, q3, max, range by time_span_min_10

SELECT 

date 

, time_span_min_10

, MIN(price) AS price_10m_min

, PERCENTILE_DISC(0.25) WITHIN GROUP (ORDER BY price) AS price_10m_q1

, PERCENTILE_DISC(0.5) WITHIN GROUP (ORDER BY price) AS price_10m_median

, PERCENTILE_DISC(0.75) WITHIN GROUP (ORDER BY price) AS price_10m_q3

, MAX(price) AS price_10m_max

, MAX(price) - MIN(price) AS price_10m_range

FROM ts_dt

GROUP BY 1, 2

ORDER BY 1, 2;


 




  (8) 10분 단위 구간별로 평균(mean), 분산(variance), 표준편차(standard deviation) 구하기


10분 단위 구간(10 minutes time span) 별로 표본 분산(sample variance)은 variance() 함수, 표본 표준편차(sample standard deviation)은 stddev() 함수를 이용하여 구하였습니다. 


10분 단위 구간별로 만약 행의 개수가 1개만 있는 경우 분산, 표준편차를 구할 수 없으므로 NULL 값을 반환하게 되는데요,  이런 경우 coalesce() 를 사용해서 '0'으로 채워주었습니다. 



-- (4) mean, variance, standard deviation by time_span_min_10

SELECT 

date 

, time_span_min_10

, AVG(price) AS price_10m_mean

, COALESCE(VARIANCE(price), 0) AS price_10m_var

, COALESCE(STDDEV(price), 0) AS price_10m_stddev

FROM ts_dt

GROUP BY 1, 2

ORDER BY 1, 2;






  (9) 10분 단위 구간별로 수량 가중 평균 가격 구하기 (amount-weighted average of price)




-- (5) amount-weighted average price

WITH price_mult_amt_tmp AS (

SELECT 

dt

, date 

, time_span_min_10

, amt

, (price * amt) AS price_mult_amt

FROM ts_dt

)

SELECT 

date 

, time_span_min_10

, ROUND((sum(price_mult_amt) / sum(amt)), 2) AS price_10m_amount_weighted_avg

FROM price_mult_amt_tmp

GROUP BY 1, 2

ORDER BY 1, 2;





많은 도움이 되었기를 바랍니다. 

이번 포스팅이 도움이 되었다면 아래의 '공감~'를 꾹 눌러주세요. :-)





Posted by R Friend R_Friend

댓글을 달아 주세요

DB에서 데이터처리나 분석을 하다보면 실수를 한다든지, 데이터 크기가 너무 크다든지, index가 안걸려있는 상태에서 join을 하려고 했다든지, cross join이 되어 연산량이 기하급수적으로 늘었다든지, 여러가지 이유로 수행시간이 너무 오래걸리는 경우가 생길 수 있습니다. 이럴 경우 강제로 돌아가고 있는 쿼리를 강제 종료 (kill active query) 시켜야 하는 상황이 생길 수 있습니다. 


이번 포스팅에서는 PostgreSQL DB, Greenplum DB 이용 시에 터미널에서 psql 로 

(1) 터미널 창에서 psql 실행하여

(2) 현재 수행되고 있는 쿼리(current active query)를 조회하고, 

(3) 특정 쿼리를 강제 종료 (kill a specific query) 시키는 방법

을 소개하겠습니다. 



(1) 먼저, 터미널을 열고 $ psql postgres  을 입력해서 psql 을 사용할 수 있는 환경으로 들어갑니다. 




ihongdon-ui-MacBook-Pro:~ ihongdon$ psql postgres

psql (11.5)

Type "help" for help.


postgres=#

 




호스트 이름, DB 이름, 사용자 ID, Password 입력은 아래 psql을 참고하세요. 



$ psql -h host_name  -d db_name  -u user_id

$ Password ********

 




(2) 터미널에서 psql 로 pg_stat_activity 테이블에서 아래의 조회 쿼리를 사용하여 현재 Postgres에서 수행 중인 쿼리(active query)를 조회해보겠습니다. 


select pid, now() - query_start as "runtime", usename, datname, state, query 

from pg_stat_activity 

where now() - query_start > '2 minutes'::interval and state = 'active' 

order by runtime desc;


위의 쿼리로 조회를 해서 강제 종료시키고자 하는 쿼리의 'pid' 를 확인합니다. 아래의 예의 경우 pid = 4941 이네요. 



postgres=#

postgres=# select pid, now() - query_start as "runtime", usename, datname, state, query from pg_stat_activity where now() - query_start > '2 minutes'::interval and state = 'active' order by runtime desc;

 

 pid  |     runtime     | usename  | datname  | state  |                                      query

------+-----------------+----------+----------+--------+----------------------------------------

 4941 | 00:34:00.595933 | ihongdon | postgres | active |              +

   |          |          |        | DROP TABLE IF EXISTS my_table;              +      

   |          |          |        | CREATE TABLE my_table AS (              +     

   |          |          |        |     SELECT              +

   |          |          |        |         a.*              +

   |          |          |        |         , b.age             +

   |          |          |        |         , b.pch_amt              +

   |          |          |        |         , b.pch_cnt              +

   |          |          |        |         , b.age              +

   |          |          |        |     FROM              +

   |          |          |        |         cust_master AS a              +

   |          |          |        |         LEFT OUTER JOIN cust_pch AS b              +

   |          |          |        |             ON  a.cust_id = b.cust_id            +

   |          |          |        | );              +

   |          |          |        |

(1 row)


postgres=#

 




(3) SELECT pg_terminate_backend(put your pid)  로 강제 종료 시키고자 하는 쿼리의 pid 를 지정해주어서 kill 시킵니다. 



postgres=# SELECT pg_terminate_backend(4941); 


pg_terminate_backend

----------------------

 t

(1 row)


postgres=#

postgres=#

 




-----------------[ Greenplum DB (GPDB) 알아두면 요긴한 팁 ] -------------------------


  • Greenplum DB(GPDB)도 현재 수행 중이 쿼리 조회하고 강제 종료하는 방법 (Postgres와 동일)



-- ssh로 GPDB 접속

> ssh gpadmin@xxx.xxx.xxx.xxx
> password: xxxxxxxxx

-- 현재 수행 중인 쿼리 조회
> select datname, username, proceed, sess_id, current_query from pg_stat_activity;


-- 현재 수행중인 쿼리 강제 종료
> select pg_cancel_backend(**put pid here**)

 



  • GPDB에서 Segment 확인하는 방법


> select gp_segment_id, count(*) from mytable group by gp_segment_id;

 



  • GPDB master, Segment 확인


> select * from pg_catalog.gp_segment_configration;

 



  • GPDB, Postgres DB 칼럼 이름 및 데이터 유형 확인 (column name and data type)


select column_name, data_type

from information_schema.columns
where table_name ='table_name'

 



  • gpperfrom db 에 접속하여 system history 테이블 조회하는 방법 (병렬처리 확인)


select

    ctime, 

    hostname, 

    avg(cpu_user + cpu_sys) cpu, 

    avg(mem_actual_used/1024/1024/1024) memogy_gigabyte, 

    avg(dist_rb_rate/1024/1024) dist_rb_megabyte, 

    avg(disk_wb_rate/1024/1024) disk_wb_megabyte, 

    avg(net_rb_rate/1024/1024) net_rb_megabyte, 

    avg(net_wb_rate/1024/1024) net_wb_megabyte

 from system_hitory

 where ctime between '2019-01-01 12:00:00' and '2019-01-01 23:59:59' 

and hostname not in ('dev1.gphd.local', 'dev2.gphd.local')

group by ctime, hostname

order by 1, 2 asc

 



  • PostgreSQL DB 서버 시작, 종료 (How to Start and Stop PostgreSQL Database Server)
- Mac OS (명령 프롬프트 창에서 Command 실행)


-- Mac OS 에서 Homebrew 사용하여 PostgreSQL 시작 (Start PostgreSQL using Homebrew on Mac OS)

$ brew services start postgresql


-- Mac OS 에서 Homebrew 사용하여 PostgreSQL 종료 (Stop PostgreSQL using Homebrew on Mac OS)

$ brew services stop postgresql

 



- Windows OS (명령 프롬프트 창에서 command 실행)


-- Windows OS 에서 PostgreSQL 시작 (Start PostgreSQL on Windows OS)

$ pg_ctl -D "C:\Program Files\PostgreSQL\9.6\data" start


-- Windows OS 에서 PostgreSQL 종료 (Stop PostgreSQL on Windows OS)

pg_ctl -D "C:\Program Files\PostgreSQL\9.6\data" stop





http://localhost:28080/main/querymonitor/hostmetrics

 


 GPDB를 UI 상에서 편리하게 모니터링 하고 관리할 수 있게 해주는 GPDB Command Center 에 대한 자세한 설명은  gpcc.docs.pivotal.io 를 참고하세요. 



[ Pivotal Greenplum Command Center Images ((https://gpcc.docs.pivotal.io/600/welcome.html) ]








많은 도움이 되었기를 바랍니다. 


Posted by R Friend R_Friend

댓글을 달아 주세요

제가 2019년 1월 30일에 Pivotal 의 Data Science Webinar 에서 발표했었던 "오픈소스 그림플럼 DB와 아파치 MADlib을 활용한 그래프 분석, 네트워크 분석 (Graph analytics with Greenplum and Apache MADlib)" 자료를 공유합니다. 


[ Agenda ]

1. Why Graph Analytics?

2. What is Graph Analytics?

3. Graph Analytics with Apache MADlib on Greenplum in parallel


presented by Hongdon Lee, Pivotal Senior Data Scientist


[ 파일 첨부: Graph Analytics with Greenplum and Apache MADlib ]

Pivotal_Graph_Analytics_w_MADlib_GPDB_20190130.pdf




개별 단위별 객체, 대상만을 놓고 나누어 분석하는 환원주의(Reductionism: Divide and conquer) 대비 전체를 하나로 놓고 관계와 연결에 주목해서 분석하면 새로운 인사이트를 얻을 수 있다는 전체론 관점(Holism: Everything has to be understood in relation to the whole)의 분석 기법으로 그래프 이론(graph theory)네트워크 분석(network analysis)을 소개하는 자료입니다. 


그래프 분석 시 도전 사항으로 복잡도가 매우 높아 연산 부하가 크고 분석이 불가능하거나 시간이 오래 걸린다는 점입니다. 이를 해결할 수 있는 대안으로 MPP(Massively Parallel Processing) 아키텍처 기반의 오픈소스 Greenplum DBApache MADlib을 이용한 그래프, 네트워크 분석의 In-Database 병렬 처리 분석을 소개하였습니다. 


Webinar 발표할 때 재미를 더하기 위해서 슬라이드 중간 중간에 제 사진도 좀 넣어보았는데요, 이렇게 파일 공유하려니 좀 쑥스럽기도 하네요. ^^;  Webinar 발표할 때는 우리나라말로 했었는데요, 글로벌 데이터 팀 구성원들한테도 공유할 생각으로 슬라이드는 영어로 만들었습니다. 중간에 수식도 많은데 영어라서 눈에 잘 안들어온다는 피드백이 있었는데요, 양해 바랍니다. ^^;;; 


알고리즘 세부 소개하는 부분은 위키피디아를 많이 참고하였습니다. 


저는 실제 프로젝트 하면서 그래프 / 네트워크 분석이 제공할 수 있는 인사이트의 유용함을 경험하기도 했구요, Greenplum DB 에서 MADlib으로 분석하면서 In-DB parallel processing의 강력함을 경험했던 지라 기회되면 꼭 한번 소개를 하고 싶었던 주제였습니다. R이나 Python으로 노트북에서 network 분석 공부할 땐 아무 문제 없다가도, 실제 기업이나 공공기관의 수 terabyte, petabyte 급 데이터를 마주하게 되면 당황하기 마련인데요, 이럴 때 사용할 수 있는 방법, 툴입니다. 


주요 장표 몇 장만 아래에 화면 캡쳐한거 소개하자면요, 



  • Everything is connected!




  • Network: Everywhere with Everything, All the time



  • What is Graph Theory?



  • Graph algorithms and measures


  • Tools for Graph Analytics



  • Apache MADlib: Scaleable, In-Database Machine Learning in SQL



  • Apache MADlib: Graph Analytics Functions




그래프/ 네트워크 분석 주제 중에서 Page Rank 에 대한 간단한 예제 SQL 코드와 Graphviz, PyGraphviz를 활용한 네트워크 시각화 코드도 공유합니다. 



[ 그래프 분석 MADlib SQL codes ]

graph_pagerank_madlib.sql


----------------------------------------------

-- Graph Analytics with Greenplum and MADlib

----------------------------------------------


-- CREATE VERTEX TABLE

DROP TABLE IF EXISTS vertex;

CREATE TABLE vertex(

id INTEGER

) DISTRIBUTED RANDOMLY;


INSERT INTO vertex 

VALUES

(0), 

(1), 

(2), 

(3), 

(4),

(5),

(6);


select * from vertex


-- CREATE EDGE TABLE

DROP TABLE IF EXISTS edge;

CREATE TABLE edge(

src INTEGER,

dest INTEGER,

user_id INTEGER

)

DISTRIBUTED BY (user_id);


INSERT INTO edge 

VALUES 

(0, 1, 1), (0, 2, 1), -- user id 1

(0, 4, 1), (1, 2, 1),

(1, 3, 1), (2, 3, 1),

(2, 5, 1), (2, 6, 1),

(3, 0, 1), (4, 0, 1),

(5, 6, 1), (6, 3, 1),

(0, 1, 2), (0, 2, 2), -- user id 2

(0, 4, 2), (1, 2, 2),

(1, 3, 2), (2, 3, 2),

(3, 0, 2), (4, 0, 2),

(5, 6, 2), (6, 3, 2);


select * from edge;



-- (1) Compute the PageRank with All IDs

DROP TABLE IF EXISTS pagerank_out, pagerank_out_summary;

SELECT madlib.pagerank(

'vertex' -- Vertex table

, 'id' -- Vertex id column

, 'edge' -- Edge table

, 'src=src, dest=dest' -- Comma delimited string of edge arguments

, 'pagerank_out' -- Output table of RageRank

, NULL -- Default damping factor (0.85)

); 


SELECT * FROM pagerank_out ORDER BY pagerank DESC;



-- (2) Compute the PageRank of vertices associated with each user using the grouping feature

DROP TABLE IF EXISTS pagerank_gr_out, pagerank_gr_out_summary;

SELECT madlib.pagerank(

'vertex' -- Vertex table

, 'id' -- Vertex id column

, 'edge' -- Edge table

, 'src=src, dest=dest' -- Comma delimited string of edge arguments

, 'pagerank_gr_out' -- Output table of PageRank

, NULL -- Default damping factor (0.85)

, NULL -- Default max iterations (100)

, 0.00000001 -- Threshold

, 'user_id'); -- Grouping column name


SELECT * FROM pagerank_gr_out ORDER BY user_id, pagerank DESC;



-- (3) Personalized PageRank of vertices {2, 4}

DROP TABLE IF EXISTS pagerank_pers_out, pagerank_pers_out_summary;

SELECT madlib.pagerank(

'vertex' -- Vertex table

, 'id' -- Vertex id column

, 'edge' -- Edge table

, 'src=src, dest=dest' -- Comma delimited string of edge arguments

, 'pagerank_pers_out' -- Output table of PageRank

, NULL -- Default damping factor (0.85)

, NULL


-- Default max iterations (100)

, NULL -- Default Threshold (1/number of vertices*1000)

, NULL -- No Grouping

, '{2, 4}' -- Personalization vertices

);


SELECT * FROM pagerank_pers_out ORDER BY pagerank DESC;


SELECT * FROM pagerank_pers_out_summary;


 




[ Graphviz 활용한 네트워크 시각화 Python codes ]

NW_visualization.py





#!/usr/bin/env python2

# -*- coding: utf-8 -*-

"""

Created on Tuesday Jau 29 2019

@author: Hongdon Lee

"""

#%% network visualization using Graphviz

import numpy as np

import pandas as pd

import pygraphviz as pgv


def run_query(query):

     import pandas as pd

     import psycopg2 as pg


    # DB Connection

     conn = pg.connect(host='localhost',

                       port='5432', 

                       dbname='gpadmin', 

                       user='gpadmin', 

                       password='pivotal')


     # Get a DataFrame

     query_result = pd.read_sql(query, conn)

     conn.close()

     return query_result



#%% Network Edge Table

query = """

select a.*, b.pagerank 

    from edge a

    left outer join pagerank_out b on a.src = b.id

;

"""


edge_pagerank = run_query(query)


#%% PageRank Values

query = """

select * from pagerank_out;

"""


pagerank_out  = run_query(query)



#%% NW Visualization using Graphviz with different size proportional to PageRank


import pygraphviz as pgv


# Generating the output flow_graph with PyGraphviz

flow_graph = pgv.AGraph(strict=False, directed=True) # directed graph


# Flow Direction(Left to Right, or Top to Bottom)

flow_graph.graph_attr['rankdir'] = 'LR' # from Left to Right


# Node Shape

flow_graph.node_attr['shape'] = 'circle'


# Making node with different size proportional to PageRank

for i in range(len(pagerank_out)):

    label_text = str(pagerank_out.id[i]) + '\n(' + str(pagerank_out.pagerank[i].round(decimals=2)) + ')'

    node_width = pagerank_out.pagerank[i]*10

    node_height = pagerank_out.pagerank[i]*10

    

    flow_graph.add_node(str(pagerank_out.id[i]), 

                        label=label_text, 

                        **{'width': str(node_width), 

                           'height': str(node_height)})


# Adding edge with different color by user_id

colors = ['blue', 'red']

 

for i in range(len(edge_pagerank)):

    if edge_pagerank.user_id[i] == 1:

        color_text = colors[0]

    else:

        color_text = colors[1]

    

    flow_graph.add_edge(str(edge_pagerank.src[i])

                        , str(edge_pagerank.dest[i])

                        , color = color_text)


#----- Finally, Draw the Network Diagram using dot program :-)

flow_graph.draw("/Users/ihongdon/Documents/nw_diagram.png", prog='dot')

 



[ Docker image를 이용해서 Greenplum, MADlib, PL/R, PL/Python 분석 환경 구성하는 방법 (싱글 노드의 로컬 기능 테스트, 공부 용도로)]

https://rfriend.tistory.com/379 

https://hub.docker.com/r/hdlee2u/gpdb-analytics



많은 도움이 되었기를 바랍니다. 


ps. 앤 해서웨이(ANNE HATHAWAY)와 연결해주실 분 계신가요? 6 degrees of separation 의 기적이 저에게도 일어날 수 있으려나요? ^^" 



Posted by R Friend R_Friend

댓글을 달아 주세요

지난 포스팅에서는 Python pandas DataFrame을 csv 파일로 다운로드 한 후에 로컬에서  PostgreSQL, Greenplum DB에 Copy해서 넣는 방법 (https://rfriend.tistory.com/457) 을 소개하였습니다. 

 이번 포스팅에서는 Python pandas DataFrame을 csv 파일로 다운로드 하는 절차 없이, sqlalchemy engine과 to_sql() 함수를 이용하여 바로 직접 PostgreSQL, Greenplum DB에 쓰는 방법을 소개하겠습니다. 이렇게 하면 다운로드하는 절차가 필요없기 때문에 좀더 간편하고 workflow가 간소화되는 장점은 있는데요, csv로 내려서 copy 하는 것 대비 속도고 조금 더 느리다는 단점이 있습니다. 



먼저 예제로 사용할 간단한 DataFrame을 만들어보겠습니다. 


# make a sample DataFrame

import pandas as pd

score = pd.DataFrame({

        'date': ['2019-07-28']*4, 

        'name': ['kim', 'lee', 'choi', 'park'], 

        'age': [19, 20, 19, 20], 

        'math_score': [91, 95, 92, 70], 

        'pass_yn': [True, True, True, False]}, 

         columns=['date', 'name', 'age', 'math_score', 'pass_yn'])



이제 'score' 라는 이름의 pandas DataFrame을 Postgresql, Greenplum DB에 'score' 라는 Table 이름으로 public schema에 생성해서 써보겠습니다. 

이때 DB connection을 하기 위해 SQLAlchemy로 DB engine 을 생성해줘야 하는데요,
engine = sqlalchemy.create_engine("postgresql://user:password@host:port/database") 
의 순서대로 자신의 DB 설정값을 입력해주면 됩니다. (port 가 5432 디폴트 값이면 생략 가능)

 

import sqlalchemy

from sqlalchemy import create_engine

# engine = sqlalchemy.create_engine("postgresql://user:password@host:port/database")

engine = create_engine("postgresql://postgres:postgres@localhost:5432/postgres") # set yours


engine.execute("DROP TABLE IF EXISTS public.score;") # drop table if exists

score.to_sql(name = 'score', 

             con = engine

             schema = 'public', 

             if_exists = 'fail', # {'fail', 'replace', 'append'), default 'fail'

             index = True, 

             index_label = 'id', 

             chunksize = 2, 

             dtype = {

                     'id': sqlalchemy.types.INTEGER()

                     'date': sqlalchemy.DateTime()

                     'name': sqlalchemy.types.VARCHAR(100)

                     'age': sqlalchemy.types.INTEGER()

                     'math_score': sqlalchemy.types.Float(precision=3)

                     'pass_yn': sqlalchemy.types.Boolean()

                     })



'if_exists' 옵션에는 {'fail', 'replace', 'append'}의 3개가 존재하고, 디폴트는 'fail' 옵션입니다. 

  • if_exists = 'fail' : 같은 이름의 Table이 존재할 경우 ValueError 가 남
  • if_exists = 'replace'같은 이름의 Table이 존재할 경우 기존 Table을 Drop하고 새로운 값을 Insert함
  • if_exists = 'append': 같은 이름의 Table이 존재할 경우 기존 Table에 추가로 새로운 값을 Insert함


index = True 로 설정해주면 pandas DataFrame의 Index도 DB Table에 insert 해주며, index_label = 'xxx'로 index의 칼럼 이름을 부여해줄 수 있습니다. 


chunksize = xx 를 설정해주면 pandas DataFrame 데이터를 xx row 개수 만큼 DB table 에 insert를 해줍니다. 설정해주지 않으면 pandas DataFrame을 통째로 한꺼번에 insert를 합니다. 


dtype 은 pandas DataFrame의 각 변수별로 DB table에 넣어줄 Data Type을 사전형(Dictionary)으로 {'column': data_type} 형식으로 설정해줄 수 있습니다. 위의 예시에서 INTEGER, DateTime(), VARCHAR(), Float(), Boolean 데이터 형태 지정하는 것을 보여주었는데요, 대/소문자, 괄호() 여부를 위의 예시처럼 똑같이 사용해야 합니다. (괄호를 빼먹거나, 대/소문자가 틀리면 에러가 납니다)

참고로, to_sql() 에서 dtype 을 칼럼 별로 설정하지 않으면 전부 'text' 데이터 형태로 해서 DB table에 입력됩니다. 


확인차, DBeaver로 PostgreSQL에 score table을 조회해보겠습니다. Python pandas의 'score' DataFrame이 PostgreSQL의 score table로 데이터가 잘 들어갔네요! 


많은 도움이 되었기를 바랍니다.

이번 포스팅이 도움이 되었다면 아래의 '공감~'를 꾹 눌러주세요. 


Posted by R Friend R_Friend

댓글을 달아 주세요

이번 포스팅에서는 Python pandas의 DataFrame을 Postgresql, Greenplum DB에 Copy 해서 집어넣는 방법을 소개하겠습니다. 

먼저 간단한 예제 pandas DataFrame을 만들어보겠습니다. 


import numpy as np

import pandas as pd


# make a DataFrame

school = pd.DataFrame({'region': ['gangnam', 'secho', 'bundang', 'mokdong'], 

                       'student_cnt': [100, 120, 150, 90], 

                       'math_score': [91, 95, 92, 93]}, 

                        columns=['region', 'student_cnt', 'math_score'])


school

Out[02]: 

    region  student_cnt  math_score

0  gangnam          100          91

1    secho          120          95

2  bundang          150          92

3  mokdong           90          93



이제 school 이라는 pandas DataFrame을 아래의 순서대로 DB에 Copy해서 넣어보겠습니다. 

(1) DataFrame을 CSV 파일로 내보내기 (export a DataFrame to CSV file)

(2) Postgresql, Greenplum DB에 연결하고 Table 만들기

(3) Postgresql, Greenplum DB의 Table에 CSV file을 Copy해서 집어넣기


  (1) DataFrame을 CSV 파일로 내보내기 (export a DataFrame to CSV file)


pandas의 to_csv() 메소드를 이용하였으며, index와 header 옵션은 False로 설정해서 CSV 파일에는 포함시키지 않도록 하겠습니다. 


school.to_csv('C:/Users/admin/Documents/data/school.csv', 

              sep=",", 

              na_rep="NaN", 

              index=False

              header=False)

 



  (2) Postgresql, Greenplum DB에 연결하고 Table 만들기

psycopg2 라이브러리를 이용해서 Postgresql, Greenplum DB에 연결해보겠습니다. 아래의 connect() 에는 본인의 DB 설정 정보를 바꾸어서 입력해주면 됩니다. 


# Postgresql DB connect using psycopg2

from psycopg2 import connect

conn = connect(host='localhost',  # set yours

               port=5432, 

               database='postgres', 

               user='postgres', 

               password='postgres')


cur = conn.cursor()


# Create a table at Postgresql public schema with school name

cur.execute("""

    DROP TABLE IF EXISTS school;

    CREATE TABLE school (

        region varchar(100), 

        student_cnt numeric, 

        math_score numeric

    )

""")

conn.commit()

 



  (3) Postgresql, Greenplum DB의 Table에 CSV file을 Copy해서 집어넣기

with open() 으로 로컬에 저장해놓은 school.csv 파일을 읽고, cursor.copy_expert() 를 이용하여 "COPY school FROM STDIN DELIMITER ',' CSV;" 쿼리문을 실행시켜서 CSV 파일을 Table 에 copy 해주겠습니다. 


query = """

    COPY school FROM STDIN DELIMITER ',' CSV;

"""


with open('C:/Users/admin/Documents/data/school.csv', 'r') as f:

    cur.copy_expert(query, f)

    

conn.commit()


# close connection

conn.close()

 


PGAdmin 에 들어가서 school 테이블을 조회해보니 아래처럼 데이터가 잘 copy 되서 들어가 있네요. 


Python에서 DB connect해서 데이터 조회하고 DataFrame으로 만들어서 한번 더 확인을 해보았습니다. 아래와 같이 데이터가 Postgresql DB의 school table에 잘 들어가 있음을 확인할 수 있습니다. 


# check 

cur.execute("SELECT * FROM school;")

school_df = cur.fetchall()

school_df

Out[39]: 

[('gangnam', Decimal('100'), Decimal('91')),

 ('secho', Decimal('120'), Decimal('95')),

 ('bundang', Decimal('150'), Decimal('92')),

 ('mokdong', Decimal('90'), Decimal('93'))] 



많은 도움이 되었기를 바랍니다. 



Posted by R Friend R_Friend

댓글을 달아 주세요

Greenplum DB에 R이나 Python, Perl, Java 등의 Procedural Language Extention을 설치해서 대용량 데이터를 In-Database 분산 병렬 처리, 분석할 수 있습니다. 

 

이번 포스팅에서는 인터넷이 되는 환경에서 다운로드한 R 패키지들을 회사/ 기관 정책상 폐쇄망으로 운영하는 환경에서 Greenplum DB에 설치하는 방법을 소개하겠습니다. 

 

1. Greenplum PL/R Extention (Procedural Language R) 설치 방법

2. Greenplum DB에 R 패키지 설치 방법

 

PL/R on Greenplum Database

 

1. Greenplum PL/R Extention 설치 방법

PL/R 은 procedural language 로서, PL/R Extension을 설치하면 Greenplum DB에서 R 프로그래밍 언어, R 패키지의 함수와 데이터셋을 사용할 수 있습니다. 

 

Greenplum DB에 PL/R 확장 언어 설치 방법은 https://gpdb.docs.pivotal.io/5180/ref_guide/extensions/pl_r.html 를 참고하였습니다. 웹 페이지의 상단에서 사용 중인 Greenplum DB version을 선택해주세요. (아래 예는 GPDB v5.18 선택 시 화면)

 

PL/R 은 패키지 형태로 되어 있으며, Pivotal Network(https://network.pivotal.io/products/pivotal-gpdb)에서 다운로드 할 수 있고, Greenplum Package Manager (gppkg) 를 사용해서 쉽게 설치할 수 있습니다. 

 

Greenplum Package Manager (gppkg) 유틸리티는 Host와 Cluster에 PL/R 과 의존성있는 패키지들을 한꺼번에 설치를 해줍니다. 또한 gppkg는 시스템 확장이나 세그먼트 복구 시에 자동으로 PL/R extension을 설치해줍니다. 

 

Greenplum PL/R Extention 설치 순서는 아래와 같습니다. 

 

(0) 먼저, Greenplum DB 작동 중이고, source greenplum_path.sh 실행,  $MASTER_DATA_DIRECTORY, $GPHOME variables 설정 완료 필요합니다. 

psql에서 Greenplum DB 버전을 확인합니다. 

psql # sql -c “select version;”

 

master host에서 gpadmin 계정으로 작업 디렉토리를 만듭니다.

(예: /home/gpadmin/packages)

 

(1) Pivotal Network에서 사용 중인 Greenplum DB version에 맞는  PL/R Extension을 다운로드 합니다. 

(예: plr-2.3.3-gp5-rhel7-x86_64.gppkg)

 

(2) 다운로드 한 PL/R Extension Package를  scp 나 sftp 를 이용해서 Greenplum DB master host로 복사합니다. (아마 회사 정책 상 DBA만 root 권한에 접근 가능한 경우가 대부분일 것이므로, 그런 경우에는 DBA에게 복사/설치 요청을 하셔야 합니다). 

$ scp plr-2.3.3-gp5-rhel7-x86_64.gppkg root@mdw:~/packages

 

(3) PL/R Extension Package를 gppkg 커맨드를 실행하여 설치합니다. (아래 예는 Linux에서 실행한 예)

$ gppkg -i plr-2.3.3-gp5-rhel7-x86_64.gppkg

 

(4) Greenplum DB를 재실행 합니다.

(GPDB를 껐다가 켜는 것이므로 DBA에게 반드시 사전 통보, 허락 받고 실행 필요합니다!)

$ gpstop -r

 

(5) Source the file $GPHOME/greenplum_path.sh

# source /usr/local/greenplum-db/greenplum_path.sh

 

R extension과 R 환경은 아래 경로에 설치되어 있습니다. 

$ GPHOME/ext/R-2.3.3/

 

(6) 각 데이터베이스가 PL/R 언어를 사용하기 위해서는 SQL 문으로 CREATE LANGUAGE  또는 createlang 유틸리티로 PL/R을 등록해주어야 합니다. (아래는 testdb 데이터베이스에 등록하는 예)

$ createlang plr -d testdb

이렇게 하면 PL/R이 untrusted language 로 등록이 되었습니다. 

 

 

참고로, Database 확인은 psql 로 \l 해주면 됩니다. 

psql # \l

 

 

 

2. Greenplum DB에 R 패키지 설치 방법 (Installing external R packages)

 

(0) 필요한 R 패키지, 그리고 이에 의존성이 있는 R 패키지를 한꺼번에 다운로드 합니다. (=> https://rfriend.tistory.com/441 참조)

 

(1) 다운로드한 R 패키지들을 압축하여 Greenplum DB 서버로 복사합니다. 

 

다운로드한 R 패키지들 조회해보겠습니다. 

[root@mdw /]# find . | grep sp_1.3-1.tar.gz
./home/gpadmin/r-pkg/sp_1.3-1.tar.gz
[root@mdw /]# exit
logout
[gpadmin@mdw tmp]$ cd ~
[gpadmin@mdw ~]$ cd r-pkg
[gpadmin@mdw r-pkg]$ ls -la
total 47032
drwxrwxr-x 2 gpadmin gpadmin    4096 Apr 23 13:17 .
drwx------ 1 gpadmin gpadmin    4096 Apr 23 13:14 ..
-rw-rw-r-- 1 gpadmin gpadmin  931812 Apr 23 12:55 DBI_1.0.0.tar.gz
-rw-rw-r-- 1 gpadmin gpadmin  794622 Apr 23 12:55 LearnBayes_2.15.1.tar.gz
-rw-rw-r-- 1 gpadmin gpadmin  487225 Apr 23 12:55 MASS_7.3-51.3.tar.gz
-rw-rw-r-- 1 gpadmin gpadmin 1860456 Apr 23 12:55 Matrix_1.2-17.tar.gz
-rw-rw-r-- 1 gpadmin gpadmin   31545 Apr 23 12:55 R6_2.4.0.tar.gz
-rw-rw-r-- 1 gpadmin gpadmin 3661123 Apr 23 12:55 Rcpp_1.0.1.tar.gz
-rw-rw-r-- 1 gpadmin gpadmin   21810 Apr 23 12:55 abind_1.4-5.tar.gz
-rw-rw-r-- 1 gpadmin gpadmin  231855 Apr 23 12:55 boot_1.3-20.tar.gz
-rw-rw-r-- 1 gpadmin gpadmin   17320 Apr 23 12:55 classInt_0.3-1.tar.gz
-rw-rw-r-- 1 gpadmin gpadmin   19757 Apr 23 12:55 class_7.3-15.tar.gz
-rw-rw-r-- 1 gpadmin gpadmin   73530 Apr 23 12:55 coda_0.19-2.tar.gz
-rw-rw-r-- 1 gpadmin gpadmin  658694 Apr 23 12:55 crayon_1.3.4.tar.gz
-rw-rw-r-- 1 gpadmin gpadmin   80772 Apr 23 12:55 deldir_0.1-16.tar.gz
-rw-rw-r-- 1 gpadmin gpadmin  128553 Apr 23 12:55 digest_0.6.18.tar.gz
-rw-rw-r-- 1 gpadmin gpadmin  582415 Apr 23 12:55 e1071_1.7-1.tar.gz
-rw-rw-r-- 1 gpadmin gpadmin  137075 Apr 23 12:55 expm_0.999-4.tar.gz
-rw-rw-r-- 1 gpadmin gpadmin  347295 Apr 23 12:55 foreign_0.8-71.tar.gz
-rw-rw-r-- 1 gpadmin gpadmin 1058430 Apr 23 12:55 gdata_2.18.0.tar.gz
-rw-rw-r-- 1 gpadmin gpadmin  758133 Apr 23 12:55 geosphere_1.5-7.tar.gz
-rw-rw-r-- 1 gpadmin gpadmin   33783 Apr 23 12:55 gmodels_2.18.1.tar.gz
-rw-rw-r-- 1 gpadmin gpadmin   12577 Apr 23 12:55 goftest_1.1-1.tar.gz
-rw-rw-r-- 1 gpadmin gpadmin  187516 Apr 23 12:55 gtools_3.8.1.tar.gz
-rw-rw-r-- 1 gpadmin gpadmin   45408 Apr 23 12:55 htmltools_0.3.6.tar.gz
-rw-rw-r-- 1 gpadmin gpadmin 1758514 Apr 23 12:55 httpuv_1.5.1.tar.gz
-rw-rw-r-- 1 gpadmin gpadmin 1052728 Apr 23 12:55 jsonlite_1.6.tar.gz
-rw-rw-r-- 1 gpadmin gpadmin   40293 Apr 23 12:55 later_0.8.0.tar.gz
-rw-rw-r-- 1 gpadmin gpadmin  359031 Apr 23 12:55 lattice_0.20-38.tar.gz
-rw-rw-r-- 1 gpadmin gpadmin  200504 Apr 23 12:55 magrittr_1.5.tar.gz
-rw-rw-r-- 1 gpadmin gpadmin 1581592 Apr 23 12:55 maptools_0.9-5.tar.gz
-rw-rw-r-- 1 gpadmin gpadmin  915991 Apr 23 12:55 mgcv_1.8-28.tar.gz
-rw-rw-r-- 1 gpadmin gpadmin   12960 Apr 23 12:55 mime_0.6.tar.gz
-rw-rw-r-- 1 gpadmin gpadmin   79619 Apr 23 12:55 polyclip_1.10-0.tar.gz
-rw-rw-r-- 1 gpadmin gpadmin  106866 Apr 23 12:55 promises_1.0.1.tar.gz
-rw-rw-r-- 1 gpadmin gpadmin  255244 Apr 23 12:55 rgeos_0.4-2.tar.gz
-rw-rw-r-- 1 gpadmin gpadmin  858992 Apr 23 12:55 rlang_0.3.4.tar.gz
-rw-rw-r-- 1 gpadmin gpadmin  639286 Apr 23 12:55 rpart_4.1-15.tar.gz
-rw-rw-r-- 1 gpadmin gpadmin 8166770 Apr 23 12:55 sf_0.7-3.tar.gz
-rw-rw-r-- 1 gpadmin gpadmin 2991469 Apr 23 12:55 shiny_1.3.2.tar.gz
-rw-rw-r-- 1 gpadmin gpadmin   24155 Apr 23 12:55 sourcetools_0.1.7.tar.gz
-rw-rw-r-- 1 gpadmin gpadmin 3485268 Apr 23 12:55 spData_0.3.0.tar.gz
-rw-rw-r-- 1 gpadmin gpadmin 1133621 Apr 23 12:55 sp_1.3-1.tar.gz
-rw-rw-r-- 1 gpadmin gpadmin 2861828 Apr 23 12:55 spatstat.data_1.4-0.tar.gz
-rw-rw-r-- 1 gpadmin gpadmin   65106 Apr 23 12:55 spatstat.utils_1.13-0.tar.gz
-rw-rw-r-- 1 gpadmin gpadmin 6598638 Apr 23 12:55 spatstat_1.59-0.tar.gz
-rw-rw-r-- 1 gpadmin gpadmin 1227625 Apr 23 12:55 spdep_1.1-2.tar.gz
-rw-rw-r-- 1 gpadmin gpadmin    2518 Apr 23 12:55 tensor_1.5.tar.gz
-rwxr-xr-x 1 gpadmin gpadmin    2326 Apr 23 13:17 test.sh
-rw-rw-r-- 1 gpadmin gpadmin  917316 Apr 23 12:55 units_0.6-2.tar.gz
-rw-rw-r-- 1 gpadmin gpadmin  564589 Apr 23 12:55 xtable_1.8-4.tar.gz

 

R 패키지들이 들어있는 폴더를 r-pkg.tar 이름으로 압축해보겠습니다. 

[gpadmin@mdw r-pkg]$ pwd
/home/gpadmin/r-pkg
[gpadmin@mdw r-pkg]$ cd ..
[gpadmin@mdw ~]$ tar cf r-pkg.tar r-pkg
[gpadmin@mdw ~]$ ls -lrt
total 47000
drwxr-xr-x 2 gpadmin gpadmin     4096 Aug 13  2018 gpconfigs
drwxr-xr-x 2 root    root        4096 Mar 22 07:02 gppkgs
drwxrwxr-x 1 gpadmin gpadmin     4096 Apr 23 12:48 gpAdminLogs
-rw-rw-r-- 1 gpadmin gpadmin      983 Apr 23 13:14 pkg.r
drwxrwxr-x 2 gpadmin gpadmin     4096 Apr 23 13:17 r-pkg
-rw-rw-r-- 1 gpadmin gpadmin 48107520 Apr 25 01:52 r-pkg.tar

 

명령 프롬프트 창에서 GPDB Docker 에서 압축한 파일을 로커로 복사 후에 ==> 다른 GPDB 서버로 복사하고 압축을 풀어줍니다. (저는 Docker 환경에서 하다보니 좀 복잡해졌는데요, 만약 로컬에서 R 패키지 다운받았으면 로컬에서 바로 GPDB 서버로 복사하면 됩니다. 압축한 R패키지 파일을 scp로 복사하거나 sftp로 업로드할 수 있으며, 권한이 없는 경우 DBA에게 요청하시구요.) 아래는 mdw에서 root 계정으로 시작해서 다운로드해서 압축한 R 패키지 파일을 scp로  /root/packages 경로에 복사하는 스크립트입니다. 

-- GPDB Docker에서 압축한 파일을 로컬로 복사하기
-- 다른 명령 프롬프트 창에서 복사해오고 확인하기

ihongdon-ui-MacBook-Pro:Downloads ihongdon$ docker cp gpdb-ds:/home/gpadmin/r-pkg.tar /Users/ihongdon/Downloads/r-pkg.tar
ihongdon-ui-MacBook-Pro:Downloads ihongdon$
ihongdon-ui-MacBook-Pro:Downloads ihongdon$
ihongdon-ui-MacBook-Pro:Downloads ihongdon$ ls -lrt
-rw-rw-r--   1 ihongdon  staff  48107520  4 25 10:52 r-pkg.tar

-- 다른 GPDB 서버로 복사하기
ihongdon-ui-MacBook-Pro:Downloads ihongdon$ scp r-pkg.tar root@mdw:~/package

-- 압축 해제
$ tar -xvf r-pkg.tar

 

Greenplum DB에 R 패키지를 설치하려면 모든 Greenplum 서버에 R이 이미 설치되어 있어야 합니다. 

여러개의 Segments 에 동시에 R 패키지들을 설치해주기 위해서 배포하고자 하는 host list를 작성해줍니다. 

# source /usr/local/greenplum-db/greenplum_path.sh
# vi hostfile_packages

 

vi editor 창이 열리면 아래처럼 R을 설치하고자 하는 host 이름을 등록해줍니다. (1개 master, 3개 segments 예시)

-- vi 편집창에서 --
smdw
sdw1
sdw2
sdw3
~
~
~
esc 누르고 :wq!

 

명령 프롬프트 창에서 mdw로 부터 root 계정으로 각 노드에 package directory 를 복사해줍니다. 

# gpscp -f hostfile_packages -r packages =:/root

 

hostfile_packages를 복사해서 hostfile_all 을 만들고, mdw를 추가해줍니다. 

-- copy
$ cp hostfile_packages  hostfile_all

-- insert mdw
$ vi hostfile_all
-- vi 편집창에서 --
mdw
smdw
sdw1
sdw2
sdw3
~
~
~
esc 누르고 :wq!

 

mdw를 포함한 모든 서버에 R packages 를 설치하는 'R CMD INSTALL r_package_name' 명령문을 mdw에서 실행합니다. (hostfile_all 에 mdw, smdw, sdw1, sdw2, sdw3 등록해놓았으므로 R이 모든 host에 설치됨)

$ pssh -f hostfile_all -v -e 'R CMD INSTALL ./DBI_1.0.0.tar.gz 
LearnBayes_2.15.1.tar.gz MASS_7.3-51.3.tar.gz Matrix_1.2-17.tar.gz 
R6_2.4.0.tar.gz Rcpp_1.0.1.tar.gz 
abind_1.4-5.tar.gz boot_1.3-20.tar.gz classInt_0.3-1.tar.gz
class_7.3-15.tar.gz coda_0.19-2.tar.gz crayon_1.3.4.tar.gz
deldir_0.1-16.tar.gz digest_0.6.18.tar.gz e1071_1.7-1.tar.gz
expm_0.999-4.tar.gz foreign_0.8-71.tar.gz gdata_2.18.0.tar.gz
geosphere_1.5-7.tar.gz gmodels_2.18.1.tar.gz goftest_1.1-1.tar.gz
gtools_3.8.1.tar.gz htmltools_0.3.6.tar.gz httpuv_1.5.1.tar.gz
jsonlite_1.6.tar.gz later_0.8.0.tar.gz lattice_0.20-38.tar.gz
magrittr_1.5.tar.gz maptools_0.9-5.tar.gz mgcv_1.8-28.tar.gz
mime_0.6.tar.gz polyclip_1.10-0.tar.gz promises_1.0.1.tar.gz
rgeos_0.4-2.tar.gz rlang_0.3.4.tar.gz rpart_4.1-15.tar.gz
sf_0.7-3.tar.gz shiny_1.3.2.tar.gz sourcetools_0.1.7.tar.gz
spData_0.3.0.tar.gz sp_1.3-1.tar.gz spatstat.data_1.4-0.tar.gz
spatstat.utils_1.13-0.tar.gz spatstat_1.59-0.tar.gz spdep_1.1-2.tar.gz
tensor_1.5.tar.gz units_0.6-2.tar.gz xtable_1.8-4.tar.gz'

 

특정 R 패키지를 설치하려고 할 때, 만약 의존성 있는 패키지 (dependencies packages) 가 이미 설치되어 있지 않다면 특정 R 패키지는 설치가 되지 않습니다. 따라서 위의 'R CMD INSTALL r-package-names' 명령문을 실행하면 설치가 되는게 있고, 안되는 것(<- 의존성 있는 패키지가 먼저 설치된 이후에나 설치 가능)도 있게 됩니다. 따라서 이 설치 작업을 수작업으로 반복해서 여러번 돌려줘야 합니다. loop 돌리다보면 의존성 있는 패키지가 설치가 먼저 설치가 될거고, 그 다음에 이전에는 설치가 안되었던게 의존성 있는 패키지가 바로 전에 설치가 되었으므로 이제는 설치가 되고, ...., ....., 다 설치 될때까지 몇 번 더 실행해 줍니다. 

 

많은 도움이 되었기를 바랍니다. 

Posted by R Friend R_Friend

댓글을 달아 주세요

앞의 포스팅에서는 공간지리 형태의 데이터셋을 import 하는 방법들을 소개하였습니다. 

 

이번 포스팅에서는 PostgreSQL, Greenplum DB의 PostGIS 에서 테이블(Table) 형태로 있는 공간지리 데이터에 대해서 (1) pg_dump로 공간지리 테이블을 백업하기(Backup), (2) pg_restore 로백업한 공간지리 테이블을 다시 불러오기 (Restore) 를 해보겠습니다. 

(* Reference: https://github.com/PacktPublishing/Mastering-PostGIS)

 

create a backup table using pgrestore

 

(1) pg_dump로 공간지리 데이터 테이블 백업하기 (Create a Backup table)

명령 프롬프트 창에서 docker로 Greenplum DB를 실행한 후에, gpadmin 계정으로 들어가서 이미 geometry 포맷으로 만들어두었던 data_import.earthquakes_subset_with_geom 테이블을 pg_dump 를 사용하여 백업해보았습니다. (host, port, user 부분은 각자의 database 설정을 입력하면 됨)

 

 

[gpadmin@mdw tmp]$ pg_dump -h localhost -p 5432 -U gpadmin -t data_import.earthquakes_subset_with_geom -c -F c -v -b -f earthquakes_subset_with_geom.backup gpadmin

pg_dump: reading extensions

pg_dump: identifying extension members

20190417:04:24:25|pg_dump-[INFO]:-reading schemas

pg_dump: reading user-defined tables

20190417:04:24:25|pg_dump-[INFO]:-reading user-defined functions

20190417:04:24:25|pg_dump-[INFO]:-reading user-defined types

20190417:04:24:25|pg_dump-[INFO]:-reading type storage options

20190417:04:24:25|pg_dump-[INFO]:-reading procedural languages

20190417:04:24:25|pg_dump-[INFO]:-reading user-defined aggregate functions

20190417:04:24:25|pg_dump-[INFO]:-reading user-defined operators

20190417:04:24:25|pg_dump-[INFO]:-reading user-defined external protocols

20190417:04:24:25|pg_dump-[INFO]:-reading user-defined operator classes

20190417:04:24:25|pg_dump-[INFO]:-reading user-defined operator families

pg_dump: reading user-defined text search parsers

pg_dump: reading user-defined text search templates

pg_dump: reading user-defined text search dictionaries

pg_dump: reading user-defined text search configurations

20190417:04:24:26|pg_dump-[INFO]:-reading user-defined conversions

20190417:04:24:26|pg_dump-[INFO]:-reading type casts

20190417:04:24:26|pg_dump-[INFO]:-reading table inheritance information

pg_dump: finding extension tables

20190417:04:24:26|pg_dump-[INFO]:-reading rewrite rules

20190417:04:24:26|pg_dump-[INFO]:-finding inheritance relationships

20190417:04:24:26|pg_dump-[INFO]:-reading column info for interesting tables

pg_dump: finding the columns and types of table "earthquakes_subset_with_geom"

20190417:04:24:26|pg_dump-[INFO]:-flagging inherited columns in subtables

20190417:04:24:26|pg_dump-[INFO]:-reading indexes

20190417:04:24:26|pg_dump-[INFO]:-reading constraints

20190417:04:24:26|pg_dump-[INFO]:-reading triggers

pg_dump: reading dependency data

pg_dump: saving encoding = UTF8

pg_dump: saving standard_conforming_strings = on

pg_dump: dumping contents of table earthquakes_subset_with_geom

[gpadmin@mdw tmp]$

 

 

(2) pg_restore 로 백업 테이블 다시 불러오기

이미 테이블로 만들어져 있는 data_import.earthquakes_subset_with_geom 테이블을 삭제한 후에, (1)번에서 백업해둔 데이터를 불러오겠습니다. 

 

테이블을 먼저 삭제해볼께요. 

-- (2) (DBeaver db tool 에서) drop table

DROP TABLE data_import.earthquakes_subset_with_geom;

 

테이블을 삭제하였으니, 이제 다시 (1)번에서 백업해두었던 데이터를 다시 불러와서 테이블을 생성(Restore a Backup table)해보겠습니다. 

-- (3) (명령 프롬프트 창에서) Restore using pg_restore

[gpadmin@mdw tmp]$ pg_restore -h localhost -p 5432 -U gpadmin -v -d gpadmin earthquakes_subset_with_geom.backup

pg_restore: connecting to database for restore

pg_restore: creating TABLE earthquakes_subset_with_geom

pg_restore: restoring data for table "earthquakes_subset_with_geom"

pg_restore: setting owner and privileges for TABLE earthquakes_subset_with_geom

[gpadmin@mdw tmp]$

 

DBeaver db tool에서 백업 테이블을 잘 불어와서 테이블이 생성이 되었는지 확인해보겠습니다. 

-- (4) (DBeaver db tool 에서) 백업 되었는지 조회 확인

SELECT * FROM data_import.earthquakes_subset_with_geom LIMIT 10;

 

백업 테이블 불어오기(restore)가 잘 되었네요. 

 

많은 도움이 되었기를 바랍니다.  

Posted by R Friend R_Friend

댓글을 달아 주세요

이번 포스팅에서는 PostgreSQL, Greenplum DB의 PostGIS에 raster2pgsql 유틸리티를 사용하여 raster data를 import하는 방법을 소개하겠습니다. 

 

 

예제로 사용할 데이터는 'Mastering PostGIS' (by Domink 외) 에서 소개된 raster format의 TIFF(Tagged Image File Format) 데이터인 'GRAY_50M_SR_OB.tif' 파일입니다.

 

데이터 형태는 왼쪽에 보는 바와 같이 4자리의 숫자와 알파벳의 조합으로 되어 있습니다. 

 

 

 

 

 

 

 

 

 

 

이 tif 파일을 탐색기에서 미리보기 해보면 왼쪽에 보는 바와 같이 회색의 세계지도 이미지 이네요. 

 

 

 

 

 

 

그럼 먼저, 명령 프롬프트 창에서 다운로드한 'GRAY_50M_SR_OB.tif' 파일을 docker cp 명령어로 Greenplum docker 의 tmp 폴더로 복사하겠습니다. 

-- (명령 프롬프트 창에서) copy 'GRAY_50M_SR_OB.tif' file to GPDB docker

docker cp /Users/ihongdon/Documents/PostGIS/data/GRAY_50M_SR_OB/GRAY_50M_SR_OB.tif  gpdb-ds:/tmp

 

다른 명령 프롬프트 창에서 Docker GPDB 의 gpadmin 계정으로 들어가서 파일이 잘 복사되었는지 확인해보겠습니다. 

-- (docker gpdb 명령 프롬프트 창에서) raster matadata 읽기

[gpadmin@mdw tmp]$ ls -la

total 123532

drwxrwxrwt  1 root    root        4096 Apr 10 13:13 .

drwxr-xr-x  1 root    root        4096 Apr  9 07:11 ..

-rw-r--r--  1     501 games   58405694 Apr  8 06:30 GRAY_50M_SR_OB.tif

[gpadmin@mdw tmp]$

 

GPDB gpadmin 명령 프롬프트 창에서 gdalinfo 명령어로 TIFF raster 파일의 메타정보를 조회해보겠습니다. 

-- (명령 프롬프트 창에서) raster 파일의 메타정보 조회 : gdalinfo

[gpadmin@mdw tmp]$ gdalinfo GRAY_50M_SR_OB.tif

Driver: GTiff/GeoTIFF

Files: GRAY_50M_SR_OB.tif

Size is 10800, 5400

Coordinate System is:

GEOGCS["WGS 84",

    DATUM["WGS_1984",

        SPHEROID["WGS 84",6378137,298.257223563,

            AUTHORITY["EPSG","7030"]],

        AUTHORITY["EPSG","6326"]],

    PRIMEM["Greenwich",0],

    UNIT["degree",0.0174532925199433],

    AUTHORITY["EPSG","4326"]]

Origin = (-179.999999999999972,90.000000000000000)

Pixel Size = (0.033333333333330,-0.033333333333330)

Metadata:

  AREA_OR_POINT=Area

  TIFFTAG_DATETIME=2014:10:18 09:28:20

  TIFFTAG_RESOLUTIONUNIT=2 (pixels/inch)

  TIFFTAG_SOFTWARE=Adobe Photoshop CC 2014 (Macintosh)

  TIFFTAG_XRESOLUTION=342.85699

  TIFFTAG_YRESOLUTION=342.85699

Image Structure Metadata:

  INTERLEAVE=BAND

Corner Coordinates:

Upper Left  (-180.0000000,  90.0000000) (180d 0' 0.00"W, 90d 0' 0.00"N)

Lower Left  (-180.0000000, -90.0000000) (180d 0' 0.00"W, 90d 0' 0.00"S)

Upper Right ( 180.0000000,  90.0000000) (180d 0' 0.00"E, 90d 0' 0.00"N)

Lower Right ( 180.0000000, -90.0000000) (180d 0' 0.00"E, 90d 0' 0.00"S)

Center      (  -0.0000000,   0.0000000) (  0d 0' 0.00"W,  0d 0' 0.00"N)

Band 1 Block=10800x1 Type=Byte, ColorInterp=Gray

[gpadmin@mdw tmp]$

 

raster2pgsql 유틸리티를 사용하여 (1) 한개의 Raster 데이터셋을 import 하는 방법과, (2) 여러개의 Raster 데이터셋들을 한꺼번에 import 하는 방법으로 나누어서 소개하겠습니다. 

(1) 한개의 Raster 데이터셋을 raster2pgsql 유틸리티로 import 하기

아래처럼 명령 프롬프트 창에서 raster2pgsql 유틸리티로 'GRAY_50M_SR_OB.tif' 파일을 import 하면 'gray_50m_sr_ob' 테이블이 생성됩니다. 더불어서, 'o_2_gray_50m_sr_ob', 'o_4_gray_50m_sr_ob'라는 미리보기 테이블이 같이 생성됩니다. (아래 소개된 SQL 문이 생성, 실행됩니다). 

 

----------------------------------------------------------------------------------------

[ raster2pgsql 인자 설명 ]
- G: 유틸리티에 의해 지원되는 GDAL 포맷 리스트 인쇄
- s: import한 raster 데이터의 SRID 설정
-t: 타일(tile)의 폭 x 높이 크기 
-P: 타일(tile)이 같은 차원을 가지도록 오른쪽/ 아래쪽의 모자란 차원만큼을 채워줌(pad) 
-d: 테이블 삭제 및 생성(Drops and creates a table)
-a: 기존 테이블에 이어서 데이터 추가(Appends data to an existing table)
-c: 새로운 테이블 생성(Creates a new table)
-p: 준비 모드 켜기. (단지 테이블만 생성되고, 데이터 importing은 안됨)
-F: raster이름의 칼럼 추가
-l: 콤마로 구분된 overview 테이블 생성 (o__raster_table_name 이름)
-I: raster 칼럼에 GIST 공간 인덱스 생성
-C: raster 데이터 importing 후에 raster 칼럼에 표준 제약 설정

Sets the standard constraints on the raster column after the raster is imported.

* reference:  https://postgis.net/docs/using_raster_dataman.html 
----------------------------------------------------------------------------------------

 

('| psql' 뒤에 host, port, user, database name 부분에는 각자의 DB환경정보 입력)

-- (명령 프롬프트에서) Import a single raster dataset using raster2pgsql

[gpadmin@mdw tmp]$ raster2pgsql -s 4326 -C -l 2,4 -F -t 2700x2700 GRAY_50M_SR_OB.tif data_import.gray_50m_sr_ob | psql -h localhost -p 5432 -U gpadmin -d gpadmin

 

Processing 1/1: GRAY_50M_SR_OB.tif

BEGIN

NOTICE:  CREATE TABLE will create implicit sequence "gray_50m_sr_ob_rid_seq" for serial column "gray_50m_sr_ob.rid"

NOTICE:  CREATE TABLE / PRIMARY KEY will create implicit index "gray_50m_sr_ob_pkey" for table "gray_50m_sr_ob"

CREATE TABLE

NOTICE:  CREATE TABLE will create implicit sequence "o_2_gray_50m_sr_ob_rid_seq" for serial column "o_2_gray_50m_sr_ob.rid"

NOTICE:  CREATE TABLE / PRIMARY KEY will create implicit index "o_2_gray_50m_sr_ob_pkey" for table "o_2_gray_50m_sr_ob"

CREATE TABLE

NOTICE:  CREATE TABLE will create implicit sequence "o_4_gray_50m_sr_ob_rid_seq" for serial column "o_4_gray_50m_sr_ob.rid"

NOTICE:  CREATE TABLE / PRIMARY KEY will create implicit index "o_4_gray_50m_sr_ob_pkey" for table "o_4_gray_50m_sr_ob"

CREATE TABLE

INSERT 0 1

INSERT 0 1

INSERT 0 1

INSERT 0 1

INSERT 0 1

INSERT 0 1

INSERT 0 1

INSERT 0 1

INSERT 0 1

INSERT 0 1

NOTICE:  Adding SRID constraint

CONTEXT:  SQL statement "SELECT  AddRasterConstraints( $1 ,  $2 ,  $3 , VARIADIC  $4 )"

PL/pgSQL function "addrasterconstraints" line 52 at RETURN

NOTICE:  Adding scale-X constraint

CONTEXT:  SQL statement "SELECT  AddRasterConstraints( $1 ,  $2 ,  $3 , VARIADIC  $4 )"

PL/pgSQL function "addrasterconstraints" line 52 at RETURN

NOTICE:  Adding scale-Y constraint

CONTEXT:  SQL statement "SELECT  AddRasterConstraints( $1 ,  $2 ,  $3 , VARIADIC  $4 )"

PL/pgSQL function "addrasterconstraints" line 52 at RETURN

NOTICE:  Adding blocksize-X constraint

CONTEXT:  SQL statement "SELECT  AddRasterConstraints( $1 ,  $2 ,  $3 , VARIADIC  $4 )"

PL/pgSQL function "addrasterconstraints" line 52 at RETURN

NOTICE:  Adding blocksize-Y constraint

CONTEXT:  SQL statement "SELECT  AddRasterConstraints( $1 ,  $2 ,  $3 , VARIADIC  $4 )"

PL/pgSQL function "addrasterconstraints" line 52 at RETURN

NOTICE:  Adding alignment constraint

CONTEXT:  SQL statement "SELECT  AddRasterConstraints( $1 ,  $2 ,  $3 , VARIADIC  $4 )"

PL/pgSQL function "addrasterconstraints" line 52 at RETURN

NOTICE:  Adding number of bands constraint

CONTEXT:  SQL statement "SELECT  AddRasterConstraints( $1 ,  $2 ,  $3 , VARIADIC  $4 )"

PL/pgSQL function "addrasterconstraints" line 52 at RETURN

NOTICE:  Adding pixel type constraint

CONTEXT:  SQL statement "SELECT  AddRasterConstraints( $1 ,  $2 ,  $3 , VARIADIC  $4 )"

PL/pgSQL function "addrasterconstraints" line 52 at RETURN

NOTICE:  Adding nodata value constraint

CONTEXT:  SQL statement "SELECT  AddRasterConstraints( $1 ,  $2 ,  $3 , VARIADIC  $4 )"

PL/pgSQL function "addrasterconstraints" line 52 at RETURN

NOTICE:  Adding out-of-database constraint

CONTEXT:  SQL statement "SELECT  AddRasterConstraints( $1 ,  $2 ,  $3 , VARIADIC  $4 )"

PL/pgSQL function "addrasterconstraints" line 52 at RETURN

NOTICE:  Adding maximum extent constraint

CONTEXT:  SQL statement "SELECT  AddRasterConstraints( $1 ,  $2 ,  $3 , VARIADIC  $4 )"

PL/pgSQL function "addrasterconstraints" line 52 at RETURN

 addrasterconstraints

----------------------

 t

(1 row)

NOTICE:  Adding SRID constraint

CONTEXT:  SQL statement "SELECT  AddRasterConstraints( $1 ,  $2 ,  $3 , VARIADIC  $4 )"

PL/pgSQL function "addrasterconstraints" line 52 at RETURN

NOTICE:  Adding scale-X constraint

CONTEXT:  SQL statement "SELECT  AddRasterConstraints( $1 ,  $2 ,  $3 , VARIADIC  $4 )"

PL/pgSQL function "addrasterconstraints" line 52 at RETURN

NOTICE:  Adding scale-Y constraint

CONTEXT:  SQL statement "SELECT  AddRasterConstraints( $1 ,  $2 ,  $3 , VARIADIC  $4 )"

PL/pgSQL function "addrasterconstraints" line 52 at RETURN

NOTICE:  Adding blocksize-X constraint

CONTEXT:  SQL statement "SELECT  AddRasterConstraints( $1 ,  $2 ,  $3 , VARIADIC  $4 )"

PL/pgSQL function "addrasterconstraints" line 52 at RETURN

NOTICE:  Adding blocksize-Y constraint

CONTEXT:  SQL statement "SELECT  AddRasterConstraints( $1 ,  $2 ,  $3 , VARIADIC  $4 )"

PL/pgSQL function "addrasterconstraints" line 52 at RETURN

NOTICE:  Adding alignment constraint

CONTEXT:  SQL statement "SELECT  AddRasterConstraints( $1 ,  $2 ,  $3 , VARIADIC  $4 )"

PL/pgSQL function "addrasterconstraints" line 52 at RETURN

NOTICE:  Adding number of bands constraint

CONTEXT:  SQL statement "SELECT  AddRasterConstraints( $1 ,  $2 ,  $3 , VARIADIC  $4 )"

PL/pgSQL function "addrasterconstraints" line 52 at RETURN

NOTICE:  Adding pixel type constraint

CONTEXT:  SQL statement "SELECT  AddRasterConstraints( $1 ,  $2 ,  $3 , VARIADIC  $4 )"

PL/pgSQL function "addrasterconstraints" line 52 at RETURN

NOTICE:  Adding nodata value constraint

CONTEXT:  SQL statement "SELECT  AddRasterConstraints( $1 ,  $2 ,  $3 , VARIADIC  $4 )"

PL/pgSQL function "addrasterconstraints" line 52 at RETURN

NOTICE:  Adding out-of-database constraint

CONTEXT:  SQL statement "SELECT  AddRasterConstraints( $1 ,  $2 ,  $3 , VARIADIC  $4 )"

PL/pgSQL function "addrasterconstraints" line 52 at RETURN

NOTICE:  Adding maximum extent constraint

CONTEXT:  SQL statement "SELECT  AddRasterConstraints( $1 ,  $2 ,  $3 , VARIADIC  $4 )"

PL/pgSQL function "addrasterconstraints" line 52 at RETURN

 addrasterconstraints

----------------------

 t

(1 row)

NOTICE:  Adding SRID constraint

CONTEXT:  SQL statement "SELECT  AddRasterConstraints( $1 ,  $2 ,  $3 , VARIADIC  $4 )"

PL/pgSQL function "addrasterconstraints" line 52 at RETURN

NOTICE:  Adding scale-X constraint

CONTEXT:  SQL statement "SELECT  AddRasterConstraints( $1 ,  $2 ,  $3 , VARIADIC  $4 )"

PL/pgSQL function "addrasterconstraints" line 52 at RETURN

NOTICE:  Adding scale-Y constraint

CONTEXT:  SQL statement "SELECT  AddRasterConstraints( $1 ,  $2 ,  $3 , VARIADIC  $4 )"

PL/pgSQL function "addrasterconstraints" line 52 at RETURN

NOTICE:  Adding blocksize-X constraint

CONTEXT:  SQL statement "SELECT  AddRasterConstraints( $1 ,  $2 ,  $3 , VARIADIC  $4 )"

PL/pgSQL function "addrasterconstraints" line 52 at RETURN

NOTICE:  Adding blocksize-Y constraint

CONTEXT:  SQL statement "SELECT  AddRasterConstraints( $1 ,  $2 ,  $3 , VARIADIC  $4 )"

PL/pgSQL function "addrasterconstraints" line 52 at RETURN

NOTICE:  Adding alignment constraint

CONTEXT:  SQL statement "SELECT  AddRasterConstraints( $1 ,  $2 ,  $3 , VARIADIC  $4 )"

PL/pgSQL function "addrasterconstraints" line 52 at RETURN

NOTICE:  Adding number of bands constraint

CONTEXT:  SQL statement "SELECT  AddRasterConstraints( $1 ,  $2 ,  $3 , VARIADIC  $4 )"

PL/pgSQL function "addrasterconstraints" line 52 at RETURN

NOTICE:  Adding pixel type constraint

CONTEXT:  SQL statement "SELECT  AddRasterConstraints( $1 ,  $2 ,  $3 , VARIADIC  $4 )"

PL/pgSQL function "addrasterconstraints" line 52 at RETURN

NOTICE:  Adding nodata value constraint

CONTEXT:  SQL statement "SELECT  AddRasterConstraints( $1 ,  $2 ,  $3 , VARIADIC  $4 )"

PL/pgSQL function "addrasterconstraints" line 52 at RETURN

NOTICE:  Adding out-of-database constraint

CONTEXT:  SQL statement "SELECT  AddRasterConstraints( $1 ,  $2 ,  $3 , VARIADIC  $4 )"

PL/pgSQL function "addrasterconstraints" line 52 at RETURN

NOTICE:  Adding maximum extent constraint

CONTEXT:  SQL statement "SELECT  AddRasterConstraints( $1 ,  $2 ,  $3 , VARIADIC  $4 )"

PL/pgSQL function "addrasterconstraints" line 52 at RETURN

 addrasterconstraints

----------------------

 t

(1 row)

 addoverviewconstraints

------------------------

 t

(1 row)

 addoverviewconstraints

------------------------

 t

(1 row)

COMMIT

[gpadmin@mdw tmp]$

[gpadmin@mdw tmp]$

 

DBeaver db tool에서 data_import.gray_50m_sr_ob 테이블을 조회해보면 아래와 같습니다. 

SELECT * FROM data_import.gray_50m_sr_ob LIMIT 10;

 

 

(2) 여러개의 Raster 데이터셋들을 한꺼번에 raster2pgsql 로 importing 하기

예제로 사용하기 위해 gdalwarp 문을 사용하여 원래의 'GRAY_50M_SR_OB.tif' raster 데이터셋을 4개의 raster 데이터셋으로 분할해보겠습니다. 

-- split into four parts using gdalwarp utility

[gpadmin@mdw tmp]$ gdalwarp -s_srs EPSG:4326 -t_srs EPSG:4326 -te -180 -90 0 0 GRAY_50M_SR_OB.tif gray_50m_partial_bl.tif

Creating output file that is 5400P x 2700L.

Processing GRAY_50M_SR_OB.tif [1/1] : 0...10...20...30...40...50...60...70...80...90...100 - done.

[gpadmin@mdw tmp]$ gdalwarp -s_srs EPSG:4326 -t_srs EPSG:4326 -te -180 0 0 90 GRAY_50M_SR_OB.tif gray_50m_partial_tl.tif

Creating output file that is 5400P x 2700L.

Processing GRAY_50M_SR_OB.tif [1/1] : 0...10...20...30...40...50...60...70...80...90...100 - done.

[gpadmin@mdw tmp]$

[gpadmin@mdw tmp]$ gdalwarp -s_srs EPSG:4326 -t_srs EPSG:4326 -te 0 -90 180 0 GRAY_50M_SR_OB.tif gray_50m_partial_br.tif

Creating output file that is 5400P x 2700L.

Processing GRAY_50M_SR_OB.tif [1/1] : 0...10...20...30...40...50...60...70...80...90...100 - done.

[gpadmin@mdw tmp]$

[gpadmin@mdw tmp]$ gdalwarp -s_srs EPSG:4326 -t_srs EPSG:4326 -te 0 0 180 90 GRAY_50M_SR_OB.tif gray_50m_partial_tr.tif

Creating output file that is 5400P x 2700L.

Processing GRAY_50M_SR_OB.tif [1/1] : 0...10...20...30...40...50...60...70...80...90...100 - done.

[gpadmin@mdw tmp]$

[gpadmin@mdw tmp]$ ls -la

total 180572

drwxrwxrwt  1 root    root        4096 Apr 16 10:35 .

drwxr-xr-x  1 root    root        4096 Apr  9 07:11 ..

-rw-r--r--  1     501 games   58405694 Apr  8 06:30 GRAY_50M_SR_OB.tif

-rw-rw-r--  1 gpadmin gpadmin 14602098 Apr 16 10:34 gray_50m_partial_bl.tif

-rw-rw-r--  1 gpadmin gpadmin 14602098 Apr 16 10:35 gray_50m_partial_br.tif

-rw-rw-r--  1 gpadmin gpadmin 14602098 Apr 16 10:34 gray_50m_partial_tl.tif

-rw-rw-r--  1 gpadmin gpadmin 14602098 Apr 16 10:35 gray_50m_partial_tr.tif

[gpadmin@mdw tmp]$

 

이제 raster2pgsql 유틸리티로 'gray_50m_partial*.tif' 처럼 파일 이름에 '*'를 사용하여 '*' 부분에 무엇이 들어있든지 간에 '*' 이외의 파일 이름이 같다면 전부 한꺼번에 importing 해보겠습니다. ('| psql' 뒤에 host, port, user, database name 부분에는 각자의 DB환경정보 입력)

-- (명령 프롬프트에서) Importing multiple rasters at once

[gpadmin@mdw tmp]$ raster2pgsql -s 4326 -C -F -t 2700x2700 gray_50m_partial*.tif  data_import.gray_50m_partial | psql -h localhost -p 5432 -U gpadmin -d gpadmin

 

Processing 1/4: gray_50m_partial_bl.tif

BEGIN

NOTICE:  CREATE TABLE will create implicit sequence "gray_50m_partial_rid_seq" for serial column "gray_50m_partial.rid"

NOTICE:  CREATE TABLE / PRIMARY KEY will create implicit index "gray_50m_partial_pkey" for table "gray_50m_partial"

CREATE TABLE

INSERT 0 1

Processing 2/4: gray_50m_partial_br.tif

INSERT 0 1

INSERT 0 1

Processing 3/4: gray_50m_partial_tl.tif

INSERT 0 1

INSERT 0 1

Processing 4/4: gray_50m_partial_tr.tif

INSERT 0 1

INSERT 0 1

INSERT 0 1

NOTICE:  Adding SRID constraint

CONTEXT:  SQL statement "SELECT  AddRasterConstraints( $1 ,  $2 ,  $3 , VARIADIC  $4 )"

PL/pgSQL function "addrasterconstraints" line 52 at RETURN

NOTICE:  Adding scale-X constraint

CONTEXT:  SQL statement "SELECT  AddRasterConstraints( $1 ,  $2 ,  $3 , VARIADIC  $4 )"

PL/pgSQL function "addrasterconstraints" line 52 at RETURN

NOTICE:  Adding scale-Y constraint

CONTEXT:  SQL statement "SELECT  AddRasterConstraints( $1 ,  $2 ,  $3 , VARIADIC  $4 )"

PL/pgSQL function "addrasterconstraints" line 52 at RETURN

NOTICE:  Adding blocksize-X constraint

CONTEXT:  SQL statement "SELECT  AddRasterConstraints( $1 ,  $2 ,  $3 , VARIADIC  $4 )"

PL/pgSQL function "addrasterconstraints" line 52 at RETURN

NOTICE:  Adding blocksize-Y constraint

CONTEXT:  SQL statement "SELECT  AddRasterConstraints( $1 ,  $2 ,  $3 , VARIADIC  $4 )"

PL/pgSQL function "addrasterconstraints" line 52 at RETURN

NOTICE:  Adding alignment constraint

CONTEXT:  SQL statement "SELECT  AddRasterConstraints( $1 ,  $2 ,  $3 , VARIADIC  $4 )"

PL/pgSQL function "addrasterconstraints" line 52 at RETURN

NOTICE:  Adding number of bands constraint

CONTEXT:  SQL statement "SELECT  AddRasterConstraints( $1 ,  $2 ,  $3 , VARIADIC  $4 )"

PL/pgSQL function "addrasterconstraints" line 52 at RETURN

NOTICE:  Adding pixel type constraint

CONTEXT:  SQL statement "SELECT  AddRasterConstraints( $1 ,  $2 ,  $3 , VARIADIC  $4 )"

PL/pgSQL function "addrasterconstraints" line 52 at RETURN

NOTICE:  Adding nodata value constraint

CONTEXT:  SQL statement "SELECT  AddRasterConstraints( $1 ,  $2 ,  $3 , VARIADIC  $4 )"

PL/pgSQL function "addrasterconstraints" line 52 at RETURN

NOTICE:  Adding out-of-database constraint

CONTEXT:  SQL statement "SELECT  AddRasterConstraints( $1 ,  $2 ,  $3 , VARIADIC  $4 )"

PL/pgSQL function "addrasterconstraints" line 52 at RETURN

NOTICE:  Adding maximum extent constraint

CONTEXT:  SQL statement "SELECT  AddRasterConstraints( $1 ,  $2 ,  $3 , VARIADIC  $4 )"

PL/pgSQL function "addrasterconstraints" line 52 at RETURN

 addrasterconstraints

----------------------

 t

(1 row)

COMMIT

[gpadmin@mdw tmp]$

[gpadmin@mdw tmp]$

 

DBeaver db tool에서 'data_import.gray_50m_partial' 테이블을 조회해 보겠습니다. 제일 마지막의 'filename' 칼럼을 보면 'gray_50m_partial_bl.tif', 'gray_50m_partial_br.tif', 'gray_50m_partial_tl.tif', 'gray_50m_partial_tr.tif' 의 4개 부분의 파일들이 들어가 있음을 알 수 있습니다. 

-- (DBeaver db tool 에서) raster file 조회
SELECT * FROM data_import.gray_50m_partial LIMIT 10;

 

많은 도움이 되었기를 바랍니다. 

Posted by R Friend R_Friend

댓글을 달아 주세요