Pages

2014년 6월 25일 수요일

[ORACLE] FUNCTION 수행과 SQL 성능문제

1.FUNCTION은 최종 추출 결과만큼만 수행하자
[Function 사용 위치 변경전 ]
select * 
from 
    (
    SELECT 
    t1.c1, t1.c2,t2.c3,FN_C2_CODENM(t2.c4) c4
    FROM FUNCTION_TABLE t1,
    C1_CODE_NM t2
    WHERE t1.c2 = 0
    AND t1.c3 = 'A'
    AND t1.c1 = t2.C1
    AND t2.c4 IN (2, 4)
    order by t2.c3
    ) z
 where rownum < 5

Call     Count CPU Time Elapsed Time       Disk      Query    Current       Rows
------- ------ -------- ------------ ---------- ---------- ---------- ----------
Parse        1    0.000        0.005          0          0          0          0
Execute      1    0.000        0.000          0          0          0          0
Fetch        2    0.484        0.574          0       3021          0          4
------- ------ -------- ------------ ---------- ---------- ---------- ----------
Total        4    0.484        0.579          0       3021          0          4


Rows     Row Source Operation
-------  -----------------------------------------------------------------------
      4  COUNT STOPKEY (cr=3021 pr=0 pw=0 time=573754 us)
      4   VIEW  (cr=3021 pr=0 pw=0 time=573749 us)
      4    SORT ORDER BY STOPKEY (cr=3021 pr=0 pw=0 time=573746 us)
   1538     HASH JOIN  (cr=714 pr=0 pw=0 time=71036 us)
   3846      TABLE ACCESS FULL FUNCTION_TABLE (cr=245 pr=0 pw=0 time=7748 us)
  40000      TABLE ACCESS FULL C1_CODE_NM (cr=469 pr=0 pw=0 time=40046 us)

********************************************************************************
-총 추출건수 : 4건

SELECT C2 FROM C2_CODE_NM WHERE C1 = :B1 

Call     Count CPU Time Elapsed Time       Disk      Query    Current       Rows
------- ------ -------- ------------ ---------- ---------- ---------- ----------
Parse        1    0.000        0.000          0          0          0          0
Execute   1538    0.016        0.028          0          0          0          0
Fetch     1538    0.000        0.012          0       2307          0        769
------- ------ -------- ------------ ---------- ---------- ---------- ----------
Total     3077    0.016        0.041          0       2307          0        769
********************************************************************************
-Function 수행횟수 : 1538건

[Function 사용 위치 변경후 ]
select c1,c2,c3, FN_C2_CODENM(c4) c4 <-- 최종 쿼리 추출시 Function 사용
from 
    (
    SELECT 
    t1.c1, t1.c2,t2.c3,t2.c4
    FROM FUNCTION_TABLE t1,
    C1_CODE_NM t2
    WHERE t1.c2 = 0
    AND t1.c3 = 'A'
    AND t1.c1 = t2.C1
    AND t2.c4 IN (2, 4)
    order by t2.c3
    ) z
 where rownum < 5

Call     Count CPU Time Elapsed Time       Disk      Query    Current       Rows
------- ------ -------- ------------ ---------- ---------- ---------- ----------
Parse        1    0.016        0.005          0          0          0          0
Execute      1    0.000        0.000          0          0          0          0
Fetch        2    0.078        0.079          0        722          0          4
------- ------ -------- ------------ ---------- ---------- ---------- ----------
Total        4    0.094        0.085          0        722          0          4


Rows     Row Source Operation
-------  -----------------------------------------------------------------------
      4  COUNT STOPKEY (cr=714 pr=0 pw=0 time=54872 us)
      4   VIEW  (cr=714 pr=0 pw=0 time=54864 us)
      4    SORT ORDER BY STOPKEY (cr=714 pr=0 pw=0 time=54860 us)
   1538     HASH JOIN  (cr=714 pr=0 pw=0 time=60290 us)
   3846      TABLE ACCESS FULL FUNCTION_TABLE (cr=245 pr=0 pw=0 time=7781 us)
  40000      TABLE ACCESS FULL C1_CODE_NM (cr=469 pr=0 pw=0 time=40047 us)

********************************************************************************
-총 추출건수 : 4건

SELECT C2 FROM C2_CODE_NM WHERE C1 = :B1 

Call     Count CPU Time Elapsed Time       Disk      Query    Current       Rows
------- ------ -------- ------------ ---------- ---------- ---------- ----------
Parse        1    0.000        0.000          0          0          0          0
Execute      4    0.000        0.000          0          0          0          0
Fetch        4    0.000        0.000          0          8          0          4
------- ------ -------- ------------ ---------- ---------- ---------- ----------
Total        9    0.000        0.000          0          8          0          4

********************************************************************************
-Function 수행횟수 : 4번 
-4건 추출 후 Fetch 단계에서 Function 수행
-불필요하게 수행되었던 FUNCTION 수행을 효과적으로 제거하여 성능개선 효과 

2.FUNCTION이 스칼라 서브쿼리에서 수행하도록 변경하자
-SQL 추출건수가 많다면 FUNCTION의 수행위치만으로는 성능개선이 힘들다.
-반복되는 입력값을 통해 동일한 결과값을 추출하는 경우 
  매번 Function을 사용하는 것 보다는 
  스칼라 서브쿼리 내에서 수행되도록 변경한다면 
  Multi Buffer(10g이후) 사용이 가능해지므로  Function의 수행횟수를 감소시켜 성능개선의 효과를 볼수 있다
-_query_execution_cache_max_size Multi Buffer 사이즈 조정가능.(충분한 검토후 사용해야함)

**입력 값에 대응하는 값의 종류가 다양하지 않고, 입력 값에 대한 결과가 항상 동일하다는 것이 보장 된다면, 
 SQL 작성시 Function 은 스칼라 서브쿼리에서 수행하도록 하여, Function 수행에 의한 부하를 감소시켜야 한다!
3.FUNCTION을 호출하는 값의 패턴을 분석하자
-Function을 스칼라 서브쿼리에서 수행하도록 변경하더라도, 
 중복값이 거의 존재하지 않는는 Unique한 값이 Function의 입력값인 경우
  Cache 효과를 거의 볼수 없으므로 성능상 유리한 점이 없다.
-오히려 Unique한 값이 입력값으로 사용되는 Function인 경우 Cache 효과는 누리지 못하면서, 내부적으로 Multi Buffer 관리 비용만 발생한다.
-따라서 Function을 호출하는 입력값의 패턴에 대한 분석 없이 Function을 스칼라 서브쿼리에서 수행하도록  SQL을 작성하는것은 바람직 하지 않다

*NUM_DISTINCT 값이 테이블 ROW수와 비슷한 경우에는 Function의 사용보다는 Outer Join으로 변경하여  수행하는것이 성능상 유리할 수 있다.
(Outer Join을 수행할 경우, 가장 유리한 조인방법 선택이 가능하고 소프트 파싱의 부하도 줄일 수 있다)
  • 패턴 분석(테이블)
    TABLE_NAMENUM_ROWSBLOCKS
    FUNCTION_TABLE100000253
  • 패턴분석(컬럼)
    COLUMN_NAMENUM_DISTINCT
    C1100000<-- 아우터 조인이 유리할 수 있다.
    C22
    C326
    C43<-- 이러한 경우에 FUNCTION을 스칼라 서브쿼리를 통해 수행하도록 하면 성능개선의 큰 효과를 볼 수 있다
*테이블 통계정보의 NUM_ROWS와 컬럼 통계정보의 NUM_DISTINCT를 보고 패턴을 파악하여 효과적인 방법을 선택하자!*
4.SELECT절에 사용된 FUNCTION을 조인으로 변경하자
-NUM_DISTINCT값이 매우 많은 경우 스칼라 서브쿼리로 변경해도 성능개선이 되지 않는다.
-이러한 경우에는 Outer Join으로 변경하여 성능을 개선시킬 수 있다

(변경전)
SELECT (SELECT fn_c1_codenm(c1) FROM DUAL), c1
FROM FUNCTION_TABLE

Call     Count CPU Time Elapsed Time       Disk      Query    Current       Rows
------- ------ -------- ------------ ---------- ---------- ---------- ----------
Parse        1    0.016        0.003          0          0          0          0
Execute      1    0.000        0.000          0          0          0          0
Fetch     1001   15.460       15.503          0     301243          0     100000
------- ------ -------- ------------ ---------- ---------- ---------- ----------
Total     1003   15.475       15.506          0     301243          0     100000


Rows     Row Source Operation
-------  -----------------------------------------------------------------------
 100000  FAST DUAL  (cr=0 pr=0 pw=0 time=104896 us)
 100000  TABLE ACCESS FULL FUNCTION_TABLE (cr=1243 pr=0 pw=0 time=79 us)

********************************************************************************

SELECT C2 FROM C1_CODE_NM WHERE C1 = :B1 

Call     Count CPU Time Elapsed Time       Disk      Query    Current       Rows
------- ------ -------- ------------ ---------- ---------- ---------- ----------
Parse        1    0.000        0.000          0          0          0          0
Execute 100000    1.888        1.773          0          0          0          0
Fetch   100000    1.154        1.166          0     300000          0     100000
------- ------ -------- ------------ ---------- ---------- ---------- ----------
Total   200001    3.042        2.939          0     300000          0     100000

********************************************************************************

(변경후)
SELECT b.c2, a.c1
FROM function_table a,
c1_code_nm b
WHERE a.c1 = b.c1(+)

Call     Count CPU Time Elapsed Time       Disk      Query    Current       Rows
------- ------ -------- ------------ ---------- ---------- ---------- ----------
Parse        1    0.000        0.001          0          0          0          0
Execute      1    0.000        0.000          0          0          0          0
Fetch     1001    0.359        0.287          0       1706          0     100000
------- ------ -------- ------------ ---------- ---------- ---------- ----------
Total     1003    0.359        0.288          0       1706          0     100000

Rows     Row Source Operation
-------  -----------------------------------------------------------------------
 100000  HASH JOIN OUTER (cr=1706 pr=0 pw=0 time=160334 us)
 100000   TABLE ACCESS FULL FUNCTION_TABLE (cr=245 pr=0 pw=0 time=56 us)
 100000   TABLE ACCESS FULL C1_CODE_NM (cr=1461 pr=0 pw=0 time=62 us)

********************************************************************************
- SQL 수행 시 Function 을 수행하면서 발생하는 10 만 번의 파싱도 동시에 제거됨으로써, 소프트 파싱으로 인한 부하도 크게 개선되었다
- Function 을 통해 10 만 번 반복적으로 Unique Scan 을 했던 C1_CODE_NM 테이블이 
    조인으로 수행되도록 SQL 을 작성하고 Hash Join 으로 처리함으로써, 반복적인 액세스를 제거하여 성능을 개선하였다.

5.WHERE절의 FUNCTION을 SELECT절로 옮기자
SELECT /*+ LEADING(T1 T2) USE_HASH(T1 T2) */
t1.c3, t2.c4
FROM FUNCTION_TABLE t1,
C1_CODE_NM t2
WHERE t1.c2 = 0
AND t1.c4 = 2
AND t1.c1 = t2.c1
AND t2.c3 IN ( 'A' )
AND fn_c2_codenm(T2.C4) BETWEEN 'A' AND 'B'

Call     Count CPU Time Elapsed Time       Disk      Query    Current       Rows
------- ------ -------- ------------ ---------- ---------- ---------- ----------
Parse        1    0.000        0.000          0          0          0          0
Execute      1    0.000        0.000          0          0          0          0
Fetch       18    1.607        1.762          0      20731          0       1667
------- ------ -------- ------------ ---------- ---------- ---------- ----------
Total       20    1.607        1.762          0      20731          0       1667

Rows     Row Source Operation
-------  -----------------------------------------------------------------------
   1667  HASH JOIN  (cr=20731 pr=0 pw=0 time=1440778 us)
  16667   TABLE ACCESS FULL FUNCTION_TABLE (cr=245 pr=0 pw=0 time=84 us)
   5000   TABLE ACCESS FULL C1_CODE_NM (cr=20486 pr=0 pw=0 time=2174393 us)

********************************************************************************

SELECT C2 FROM C2_CODE_NM WHERE C1 = :B1 

Call     Count CPU Time Elapsed Time       Disk      Query    Current       Rows
------- ------ -------- ------------ ---------- ---------- ---------- ----------
Parse        1    0.000        0.000          0          0          0          0
Execute  10000    0.250        0.170          0          0          0          0
Fetch    10000    0.078        0.100          0      20000          0      10000
------- ------ -------- ------------ ---------- ---------- ---------- ----------
Total    20001    0.328        0.270          0      20000          0      10000

********************************************************************************

-SELECT count(*) FROM c1_code_nm t2 WHERE t2.c3 IN ('A') 의  수행결과는 5,000건이다.
-따라서 위의 조건으로 걸러낸 결과에 대해서 Function이 수행될것이라 생각했지만 실제로는 10,000번이 수행되었다
-그 이유는 Optimizer 가 내부적으로 BETWEEN 조건의 경우 다음과 같이 변경하여 처리하기 때문이다.

Execution Plan
-----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=ALL_ROWS (Cost=178 Card=12 Bytes=264)
   1    0   HASH JOIN (Cost=178 Card=12 Bytes=264)
   2    1     TABLE ACCESS (FULL) OF 'FUNCTION_TABLE' (TABLE) (Cost=62 Card=17K Bytes=195K)
   3    1     TABLE ACCESS (FULL) OF 'C1_CODE_NM' (TABLE) (Cost=115 Card=13 Bytes=130)
-----------------------------------------------------------

Predicate information (identified by operation id):
-----------------------------------------------------------
   1 - access("T1"."C1"="T2"."C1")
   2 - filter("T1"."C4"=2 AND "T1"."C2"=0)
   3 - filter("T2"."C3"='A' AND "FN_C2_CODENM"("T2"."C4")>='A' AND "FN_C2_CODENM"("T2"."C4")<='B')
-----------------------------------------------------------



-where 절의 function을 select 절로 옮기자
SELECT /*+ NO_MERGE(A) */ *
FROM (
    SELECT /*+ LEADING(T1 T2) USE_HASH(T1 T2) */
    t1.c3,
    t1.c4,
    fn_c2_codenm(t2.c4) AS ft2c4
    FROM FUNCTION_TABLE t1,
    C1_CODE_NM t2
    WHERE t1.c2 = 0
    AND t1.c4 = 2
    AND t1.c1 = t2.c1
    AND t2.c3 IN ( 'A' )
    ) A
WHERE ft2c4 BETWEEN 'A' AND 'B' 

Call     Count CPU Time Elapsed Time       Disk      Query    Current       Rows
------- ------ -------- ------------ ---------- ---------- ---------- ----------
Parse        1    0.000        0.000          0          0          0          0
Execute      1    0.000        0.000          0          0          0          0
Fetch       18    1.825        1.849          0      24099          0       1667
------- ------ -------- ------------ ---------- ---------- ---------- ----------
Total       20    1.825        1.849          0      24099          0       1667


Rows     Row Source Operation
-------  -----------------------------------------------------------------------
   1667  VIEW  (cr=24099 pr=0 pw=0 time=1689310 us)
   1667   HASH JOIN  (cr=20731 pr=0 pw=0 time=1453056 us)
  16667    TABLE ACCESS FULL FUNCTION_TABLE (cr=245 pr=0 pw=0 time=88 us)
   5000    TABLE ACCESS FULL C1_CODE_NM (cr=20486 pr=0 pw=0 time=2255478 us)

********************************************************************************

SELECT C2 FROM C2_CODE_NM WHERE C1 = :B1 

Call     Count CPU Time Elapsed Time       Disk      Query    Current       Rows
------- ------ -------- ------------ ---------- ---------- ---------- ----------
Parse        1    0.000        0.000          0          0          0          0
Execute  11701    0.218        0.200          0          0          0          0
Fetch    11701    0.062        0.117          0      23402          0      11701
------- ------ -------- ------------ ---------- ---------- ---------- ----------
Total    23403    0.281        0.317          0      23402          0      11701

********************************************************************************

Execution Plan
-----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=ALL_ROWS (Cost=178 Card=12 Bytes=24K)
   1    0   VIEW (Cost=178 Card=12 Bytes=24K)
   2    1     HASH JOIN (Cost=178 Card=12 Bytes=264)
   3    2       TABLE ACCESS (FULL) OF 'FUNCTION_TABLE' (TABLE) (Cost=62 Card=17K Bytes=195K)
   4    2       TABLE ACCESS (FULL) OF 'C1_CODE_NM' (TABLE) (Cost=115 Card=13 Bytes=130)
-----------------------------------------------------------

Predicate information (identified by operation id):
-----------------------------------------------------------
   2 - access("T1"."C1"="T2"."C1")
   3 - filter("T1"."C4"=2 AND "T1"."C2"=0)
   4 - filter("T2"."C3"='A' AND "FN_C2_CODENM"("T2"."C4")>='A' AND "FN_C2_CODENM"("T2"."C4")<='B')
-----------------------------------------------------------
-인라인뷰 바깥쪽의 조건이 인라인뷰 안으로 침투되어 성능저하 발생 
-Function 을 Select 절에서 수행해놓고 나중에 이 값을 이용해 뷰 바깥에서 조건을 처리할 목적으로 SQL 을 재 작성했지만,
 QUERY TRANSFORMATION 에 의해 FPD 가 발생함으로써, Where 절과 Select 절의Function 까지 수행하여 수행횟수가 증가함 


-select절의 function을 스칼라 서브쿼리로 사용
SELECT /*+ NO_MERGE(A) */
*
FROM ( SELECT /*+ LEADING(T1 T2) USE_HASH(T1 T2) */
        t1.c3, 
        t2.c4,
        (SELECT fn_c2_codenm(T2.C4) FROM DUAL) AS ft2c4
        FROM FUNCTION_TABLE t1 ,
        C1_CODE_NM t2
        WHERE t1.c2 = 0
        AND t1.c4 = 2
        AND t1.c1 = t2.c1
        AND t2.c3 IN ( 'A' )
        ) A
WHERE ft2c4 between 'A' AND 'B'

Call     Count CPU Time Elapsed Time       Disk      Query    Current       Rows
------- ------ -------- ------------ ---------- ---------- ---------- ----------
Parse        1    0.000        0.004          0          0          0          0
Execute      1    0.000        0.000          0          0          0          0
Fetch       18    0.062        0.047          0        733          0       1667
------- ------ -------- ------------ ---------- ---------- ---------- ----------
Total       20    0.062        0.051          0        733          0       1667


Rows     Row Source Operation
-------  -----------------------------------------------------------------------
   1667  VIEW  (cr=733 pr=0 pw=0 time=45879 us)
   1667   FILTER  (cr=733 pr=0 pw=0 time=45873 us)
   1667    HASH JOIN  (cr=731 pr=0 pw=0 time=35262 us)
  16667     TABLE ACCESS FULL FUNCTION_TABLE (cr=245 pr=0 pw=0 time=57 us)
   5000     TABLE ACCESS FULL C1_CODE_NM (cr=486 pr=0 pw=0 time=10047 us)
      1    FAST DUAL  (cr=0 pr=0 pw=0 time=2 us)

********************************************************************************

SELECT C2 FROM C2_CODE_NM WHERE C1 = :B1 

Call     Count CPU Time Elapsed Time       Disk      Query    Current       Rows
------- ------ -------- ------------ ---------- ---------- ---------- ----------
Parse        1    0.000        0.000          0          0          0          0
Execute      1    0.000        0.000          0          0          0          0
Fetch        1    0.000        0.000          0          2          0          1
------- ------ -------- ------------ ---------- ---------- ---------- ----------
Total        3    0.000        0.000          0          2          0          1

********************************************************************************

-Function 수행 후 비교하는 조건이 사라지면서 FPD(Filter Push Down) 가 발생하지 않은 것을 알 수 있다.

FUNCTION의 사용
FUNCTION의 사용은 프로그램 개발이나 유지보수 등 여러 측면에서 효율적이지만,
반대로 비효율적으로 사용된 FUNCTION은 DB 성능에 큰 악영향을 미칠 수 있다.
FUNCTION의 동작방식을 정확히 이해하고, FUNCTION을 사용할 때 항상 효율적으로
수행되도록 SQL을 작성해야 한다.

출처 : http://wiki.gurubee.net/pages/viewpage.action?pageId=27427586

[ORACLE] 저장형 함수를 사용할 때 튜닝방법

-- 1. 수행쿼리 

SELECT PRODUCT_CD,
       PRODUCT_NAME,
       SUM(GET_AVG_STOCK(PRODUCT_CD, :B1, :B2)) AVG_STOCK
FROM   PRODUCT
WHERE  CATEGORY_CD = ’20’
GROUP BY PRODUCT_CD,
         PRODUCT_NAME
;

-- 2. ’GET_AVG_STOCK’ Stored_Function

CREATE OR REPLACE FUNCTION GET_AVG_STOCK(p_start_date IN DATE,
                                         p_end_date   IN DATE,
                                         p_product_cd IN VARCHAR2) RETURN  NUMBER 
AS
    v_ret_val NUMBER;
    
    BEGIN
        SELECT SUM(STOCK_QTY) / (V_START_DATE . V_END_DATE))
        INTO   v_ret_val
        FROM   PROD_STOCK
        WHERE  PRODUCT_CD = p_product_cd
        AND    STOCK_DATE BETWEEN p_start_date 
                          AND     p_end_date;                          
    EXCEPTION 
        WHEN OTHERS THEN
            v_ret_val := 0;
    END GET_AVG_STOCK;
    
    RETURN RET_VAL;
END GET_AVG_STOCK;


대략 1,000만건의 데이터가 들어있는 PRODUCT 테이블에서 CATEGORY_CD가 20인 10만건의 데이터를 

GROUP BY하여SUM값을 가져올 때 내부적으로 GET_AVG_STOCK 펑션을 10만번 Call하기 때문에 현재 

응답속도가 느린데 이를 어떻게 개선할지.....

내용을 먼저 정리해 드리자면 다음과 같습니다.

1) Function을 Scalar Subquery화 하기    
   - Function을 Scalar Subquery로 사용하면 동일한 OUTPUT을 One-Buffer가 아닌 
     Multi-Buffer에 저장함    

2) Deterministic Function 사용하기       
   - Oracle 10.2 버전부터 사용 가능 
   - Function을 Deterministic 으로 선언하면 같은 INPUT값에 대해서는 OUTPUT도 동일한 결과로
     인식하여 FUNCTION Call을 안함     

여기서 중요하게 봐야할 포인트가 바로 스칼라 서브쿼리나 DETERMINISTIC Function 모두 

Multi-Buffer에 저장을 하여 같은 값들에 대해서는 ’Function Call’을 안한다는 것입니다.

9i까지만 해도 실행계획에서 FILTER처리가 되면 One-Buffer 효과를 볼 수 있어서 재사용을 할 수 

있었는데요.테스트 결과 10g R2에서는 One-Buffer를 지원안하고 Multi-Buffer를 지원하는 것으로 

확인되었습니다. 여기서 One-Buffer에 대해 간략하게 그림으로 설명드리겠습니다.






그림에서 보시는것처럼 같은 FILTER처리를 하면 이전값과 다음값이 같을 경우 One-Buffer를 
이용하여 액세스를 안하고 처리를 합니다.

여기서 중요한것은 One-Buffer만 사용할 수 있기 때문에 이전값과 다음값의 비교만 가능해서 
만약 FILTER 처리를 하는 값이 정렬이 안되어 있을경우 효과를 크게 못본다는 것입니다.
이 개념은 동일하게 9i버전까지 패키지콜에서도 동일하게 사용된 개념인데요.
이것이 ’Oracle 10g R2’에서부터는 Multi-Buffer를 지원하는 것으로 확인되었습니다.

또 하나 ’Oracle 10g’에서 새롭게 바뀐점은 바로 ’Fast Dual’ Optimizer Plan을 사용한다는 
것입니다.
이 내용 또한 스크랩을 해서 소개해 드리겠습니다.





이제 스칼라 서브쿼리를 적용하여 문제의 쿼리를 접근해 보겠습니다.
-- 1. 수정 전

SELECT PRODUCT_CD,
       PRODUCT_NAME,
       SUM(GET_AVG_STOCK(PRODUCT_CD, :B1, :B2)) AVG_STOCK
FROM   PRODUCT
WHERE  CATEGORY_CD = ’20’
GROUP BY PRODUCT_CD,
         PRODUCT_NAME


-- 2. 수정 후(스칼라 서브쿼리 적용)

SELECT PRODUCT_CD,
       PRODUCT_NAME,
       SUM((SELECT GET_AVG_STOCK(PRODUCT_CD, :B1, :B2)
            FROM   DUAL)) AVG_STOCK
FROM   PRODUCT
WHERE  CATEGORY_CD = ’20’
GROUP BY PRODUCT_CD,
         PRODUCT_NAME
;

Execution Plan
--------------------------------------------------------------------------------
   0      SELECT STATEMENT Optimizer=ALL_ROWS  
   1    0   FAST DUAL    2    0   SORT (GROUP BY)
   3    2     TABLE ACCESS (BY INDEX ROWID) OF ’PRODUCT’ (TABLE)  
   4    3       INDEX (RANGE SCAN) OF ’PRODUCT_N1’ (INDEX)    
   
보시는것처럼 패키지부분을 스칼라 서브쿼리로 바꾼 결과 실행계획에 ’FAST DUAL’이 나온것을 

보실 수 있습니다.
즉, 2가지가 변경이 되었는데요. 

1) 패키지의 OUTPUT내용을 Multi-Buffer에 저장하여 같은값들에 대해서는 ’Function Call’을 안함
2) ’Function’에 대해서 ’FAST DUAL’로 연산하기 때문에 빠른 응답속도를 보임
실제 ’Function’에서 내부적으로 돌아가는 부분을 Trace보면 다음과 같습니다.

-- 1. 수정 전

Call     Count CPU Time Elapsed Time       Disk      Query    Current       Rows
------- ------ -------- ------------ ---------- ---------- ---------- ----------
Parse        1    0.000        0.000          0          0          0          0
Execute 100000   18.000       35.000          0          0          0          0
Fetch   100000   98.000      195.000       2317    8295211         40        500------- ------ -------- ------------ ---------- ---------- ---------- ----------
Total   400001  116.000      230.000       2317    8295211         40        500


-- 2. 수정 후

Call     Count CPU Time Elapsed Time       Disk      Query    Current       Rows
------- ------ -------- ------------ ---------- ---------- ---------- ----------
Parse        1    0.000        0.000          0          0          0          0
Execute   1000    4.000        5.000          0          0          0          0
Fetch     1000   17.000       18.000        250     480000          0        500------- ------ -------- ------------ ---------- ---------- ---------- ----------
Total     2001   21.000       23.000        250     480000          0        500

여기선 가상으로 Count를 1000으로 잡았는데요. 실제 업무에서도 같은 값이 계속 발생하면 
위에서 보신것처럼 Count를 줄여서 응답시간을 획기적으로 줄일 수 있습니다.

이 글을 읽으시는 개발자 분들 중에서도 현재 사용하는 오라클 버전이 ’10g’이시면 
스칼라 서브쿼리를 사용하셔서 응답시간을 줄이실 수 있을거라 생각합니다.

출처 : http://www.gurubee.net/article/14081

[자료] 2014 웹 개발 현황

2014년 웹 개발 현황(State of Web Development 2014) 

앞으로는 어떻게 될지 아래 링크 사이트에서 확인해보세요.

http://blog.xebia.com/2014/06/06/state-of-web-development-2014/



[Oracle] XMLAGG, LISTAGG 사용하기

LISTAGG 함수가 도입되기 전에 동일 기능을 구현하기 위해 다양한 기법들이 사용되었다. 

아래와 같이 데이터를 생성하자.
CREATE TABLE t1 (c1 NUMBER(1), c2 VARCHAR2(2));

INSERT INTO t1 VALUES (1, '01');
INSERT INTO t1 VALUES (2, '02');
INSERT INTO t1 VALUES (2, '03');
INSERT INTO t1 VALUES (3, '04');
INSERT INTO t1 VALUES (3, '04');
INSERT INTO t1 VALUES (3, '05');
INSERT INTO t1 VALUES (3, '06');

① 11g를 사용한다면 LISTAGG 함수를 사용하면 된다. 집계함수(1번)와 분석함수(2번) 형태로 사용이 가능하다.
-- 1
SELECT   a.c1,
         LISTAGG (a.c2, ',') WITHIN GROUP (ORDER BY a.c2) AS c2
    FROM t1 a
GROUP BY a.c1;

 C1 C2          
--- ------------
  1 01          
  2 02,03       
  3 04,04,05,06 

3 rows selected.

-- 2
SELECT a.c1,
       LISTAGG (a.c2, ',') WITHIN GROUP (ORDER BY a.c2) OVER (PARTITION BY A.c1) AS c2
  FROM t1 a;

 C1 C2          
--- ------------
  1 01          
  2 02,03       
  2 02,03       
  3 04,04,05,06 
  3 04,04,05,06 
  3 04,04,05,06 
  3 04,04,05,06 

7 rows selected.

② WM_CONCAT 함수는 WMSYS 유저가 내부적으로 사용한다. (SQL Reference에 없다...--;) LISTAGG보다 성능은 떨어지지만 추가 기능(DISTINCT 구문, 분석함수 누적, KEEP 절)을 지원한다. 4번 방식을 이용하면 정렬도 가능하다. 
-- 1
SELECT   a.c1,
         wmsys.wm_concat (a.c2) AS c2
    FROM t1 a
GROUP BY a.c1;

 C1 C2          
--- ------------
  1 01          
  2 02,03       
  3 04,06,05,04 

3 rows selected.

-- 2
SELECT   a.c1,
         wmsys.wm_concat (DISTINCT a.c2) AS c2
    FROM t1 a
GROUP BY a.c1;

 C1 C2          
--- ------------
  1 01          
  2 02,03       
  3 04,05,06    

3 rows selected.

-- 3
SELECT a.c1,
       wmsys.wm_concat (a.c2) OVER (ORDER BY a.c2) AS c2
  FROM t1 a;

 C1 C2                    
--- ----------------------
  1 01                    
  2 01,02                 
  2 01,02,03              
  3 01,02,03,04,04        
  3 01,02,03,04,04        
  3 01,02,03,04,04,05     
  3 01,02,03,04,04,05,06  

7 rows selected.

-- 4
SELECT   a.c1,
         MAX (CAST (a.c2 AS VARCHAR2 (4000))) as c2
    FROM (SELECT a.c1,
                 wmsys.wm_concat (a.c2) OVER (PARTITION BY a.c1 ORDER BY a.c2) AS c2
            FROM t1 a) a
GROUP BY a.c1;

 C1 C2          
--- ------------
  1 01          
  2 02,03       
  3 04,04,05,06 

3 rows selected.

-- 5
SELECT   a.c1,
         wmsys.wm_concat (a.c2) KEEP (DENSE_RANK FIRST ORDER BY a.c2) AS c2
    FROM t1 a
GROUP BY a.c1;

 C1 C2          
--- ------------
  1 01          
  2 02          
  3 04,04       

3 rows selected.

③ 10g에서는 XMLAGG 함수를 사용해도 된다. 
SELECT   a.c1,
         SUBSTR (XMLAGG (XMLELEMENT (a, ',', a.c2) ORDER BY a.c2).EXTRACT ('//text()'), 2) AS c2
    FROM t1 a
GROUP BY a.c1;

 C1 C2          
--- ------------
  1 01          
  2 02,03       
  3 04,04,05,06 

3 rows selected.

④ 후임자를 괴롭히고 싶다면 MODEL 절을 사용해도 된다...--;
SELECT   a.c1,
         RTRIM (a.c2, ',') as c2
    FROM (SELECT c1,
                 c2,
                 rn
            FROM t1 a
           MODEL PARTITION BY (a.c1)
                 DIMENSION BY (ROW_NUMBER() OVER (PARTITION BY a.c1 ORDER BY a.c2) AS rn)
                 MEASURES (CAST (a.c2 AS VARCHAR2(4000)) AS c2)
                 RULES (c2[ANY] ORDER BY rn DESC = c2[CV()] || ',' || c2[CV()+1])) a
   WHERE a.rn = 1
ORDER BY a.c1;

 C1 C2          
--- ------------
  1 01          
  2 02,03       
  3 04,04,05,06 

3 rows selected.

⑤ 9i에서는 전통적 방식인 ROW_NUMBER와 SYS_CONNECT_BY_PATH 조합을 사용하면 된다. 
SELECT     a.c1,
           SUBSTR (MAX (SYS_CONNECT_BY_PATH (a.c2, ',')), 2) AS c2
      FROM (SELECT a.c1,
                   a.c2,
                   ROW_NUMBER () OVER (PARTITION BY a.c1 ORDER BY a.c2) AS rn
              FROM t1 a) a
START WITH a.rn = 1
CONNECT BY a.c1 = PRIOR a.c1
       AND a.rn - 1 = PRIOR a.rn
  GROUP BY a.c1
  ORDER BY a.c1;

 C1 C2          
--- ------------
  1 01          
  2 02,03       
  3 04,04,05,06 

3 rows selected.

참조URL : http://tyboss.tistory.com/entry/Oracle-XMLAGG-LISTAGG-%EC%82%AC%EC%9A%A9%ED%95%98%EA%B8%B0


2014년 6월 24일 화요일

[JAVASCRIPT] JAVASCRIPT 를 head 에 넣는 것과, html 전에 넣는 것과 차이?

자바스크립트는 문서의 head 부분 (<head> 와 </head> 태그 사이) 또는 body (<body> 와 </body> 태그 사이)에 넣을수 있는데요 어쨋거나 가능하다면 JavaScript는 head에 넣는것이 좋습니다
<html><head><title>My Page</title>
<script language="javascript" type="text/javascript">
function myFunction() {
    alert('Hello world');
}
</script>
</head>
<body>
<a href="javascript:myFunction();">Click here</a>
</body>
</html>
head가 body 보다 먼저 로딩되기때문에, 자바코드를 head에 넣으면 언제든 필요한 때 사용 가능해집니다. 예를 들면 다음의 코드같은 경우 페이지가 완전히 로딩되면 작동을 하겠지만 만일 사용자가 자바코드의 function이 로딩되기 전에 링크를 클릭한다면 에러가 발생합니다. 만일 페이지가 매우 크거나 로딩되는데 시간이 걸린다면 문제가 될수 있습니다.
<html><head><title>My Page</title>
</head>
<body>
<a href="javascript:myFunction();">Click here</a>
<script language="javascript" type="text/javascript">
function myFunction() {
    alert('Hello world');
}
</script>
</body>
</html>

[JAVASCRIPT] 웹사이트 속도 개선을 위한 팁들

Web Assets – Tips for Better Organization and Performance

과거에는 프로젝트 리소스(이미지, css 등) 을 최소화하기 위해 엄청난 시간을 들였습니다. 이제는 인터넷이 빨라져서 훨씬 큰 플래쉬 파일이나 동영상, 이미지들을 사용할 수 있게 되었습니다. 하지만 모바일이 중요해지면서, 다시 과거로 돌아간 모양새 입니다. 최적화를 잘해서 반응속도를 높이는 일이 중요해 졌습니다.

Images

적당한 사이즈를 사용하기

종종 한 파일을 웹사이트 여러군데서 사용합니다. 예를 들어 쇼핑몰에서 모든 제품들은 이미지가 있습니다. 각 제품의 이미지를 보여주는 페이지가, 제품 리스트 페이지, 제품의 상세 설명 페이지, 또는 제품의 확대 이미지를 보는 페이지, 3개의 페이지가 있다고 해봅시다.
만약 이 세 페이지에서 같은 이미지 파일을 사용한다면, 브라우저는 제품 리스트 페이지에서도 아주 큰 파일을 다운 받아야 할 겁니다. 만약 원본 이미지가 1MB 정도이고 페이지당 10개의 제품을 보여준다면, 유저는 10MB 의 이미지를 다운받아야 합니다. 좋지 않죠. 가능하면 필요에 따라 여러 이미지를 만드는게 좋습니다. 그리고 유저가 사용중인 기기의 해상도를 아는 것도 도움이 됩니다. iPhone 에서 사이트를 열었을 때, 엄청나게 큰 헤더 이미지를 보여줄 필요가 없습니다. CSS media queries 를 사용해서 작은 이미지 파일을 사용하게 할 수 있습니다.
@media only screen
and (min-device-width : 320px)
and (max-device-width : 480px) {
    .header {
        background-image: url(../images/background_400x200.jpg);
    }
}

Compression (압축)

적당한 크기의 이미지를 보여주는 것만으로 충분하지 않을 때가 있습니다. 어떤 파일 포맷들은 손실 없이 압축을 할 수가 있습니다. 압축하는데 사용할 수 있는 툴들이 많이 있습니다. 포토샵만 해도 Save for Web and Devices 기능을 제공하죠



이 화면에 많은 기능들이 있습니다. 하지만 가장 중요한 것은 Quality 옵션입니다. 80% 정도로 설정하면 파일 사이즈를 엄청나게 줄일 수 있습니다.
물론 프로그래밍 상으로 압축을 할 수도 있지만, 개인적으로는 포토샵에서 압축하는 것을 선호합니다. PHP 로 압축하는 방법은 다음과 같습니다:
function compressImage($source, $destination, $quality) {
    $info = getimagesize($source);
    switch($info['mime']) {
        case "image/jpeg":
            $image = imagecreatefromjpeg($source);
            imagejpeg($image, $destination, $quality);
        break;
        case "image/gif":
            $image = imagecreatefromgif($source);
            imagegif($image, $destination, $quality);
        break;
        case "image/png":
            $image = imagecreatefrompng($source);
            imagepng($image, $destination, $quality);
        break;
    }
}
compressImage('source.png', 'destination.png', 85);

Sprites

서버로의 요청 횟수를 줄여서 사이트의 체감 반응 속도를 높일 수 있습니다. 이미지 하나 하나를 다운 받기 위해, 서버로 요청을 해야 합니다. 여러개의 이미지를 하나로 합칠 수 있으면, 서버로 파일 요청을 한번만 해도 되게 됩니다. 여러개의 이미지를 담은 이미지를 sprite(스프라이트) One 라고 부릅니다. background-positionCSS 스타일을 이용해서 스파라이트에서 원하는 위치를 보여줍니다. Twitter Bootstrap도 내부 아이콘을 표현하기 위해서 스프라이트를 사용합니다:

그리곤 CSS 에서 이 스프라이트 내의 특정 영역을 다음과 같이 보여줍니다:
.icon-edit {
    background-image: url("../img/glyphicons-halflings-white.png");
    background-position: -96px -72px;
}

Caching

브라우저의 캐슁을 잘 이용하세요. 개발중에는 캐슁기능 때문에 골탕을 먹기도 하지만요, 사이트의 속도를 높이는데는 아주 중요한 기능입니다. 모든 브라우저는 이미지, 자바스크립트 그리고 CSS 파일을 캐슁합니다. 캐슁을 설정하는 방법이 여러가지가 있는데, 자세한 방법을 보시려면 이 문서 를 보시기를 추천합니다. 일반적으로 헤더에 원하는 값을 설정해서 캐슁 방식을 조절할 수 있습니다:
$expire = 60 * 60 * 24 * 1;// seconds, minutes, hours, days
header('Cache-Control: maxage='.$expire);
header('Expires: '.gmdate('D, d M Y H:i:s', time() %2B $expire).' GMT');
header('Last-Modified: '.gmdate('D, d M Y H:i:s').' GMT');

Prefetching (미리 다운 받기)

HTML5 가 점점 진보하고 있습니다.[prefetching][6] 라는 기능이 있어서, 브라우저에게 어떤 파일들이 앞으로 곧 필요하게 될테니 다운받으라고 명령하는 역활을 합니다.

Data URI Scheme / Inline Images

몇년전에 간단한 web page, 를 만들었습니다. HTML 파일 하나로 페이지를 만들어야 했고, 몇개의 이미지를 첨부할 필요가 있었습니다. Data URI scheme 를 이용해서 이미지를 base64 encoding 된 스트링으로 변환해서 img 의 src 속성으로 설정하였습니다:
이 방법을 이용하면, 이미지가 HTML 에 포함되어, 서버로의 HTTP 요청을 한 회 줄일 수 있습니다. 물론 이미지가 크다면 변환된 스트링도 정말 길겁니다. PHP 로 이미지를 base64 스트링으로 변환하려면 다음과 같이 할 수 있습니다:
$picture = fread($fp,filesize($file));
fclose($fp);
// base64 encode the binary data, then break it
// into chunks according to RFC 2045 semantics
$base64 = base64_encode($picture);
$tag = '';
$css = 'url(data:image/jpg;base64,'.str_replace("
", "", $base64).'); ';
위 방법이 유용할 때가 있는데, IE 에서는 잘 작동하지 않을 때가 있을 수 있으니 조심하세요.

CSS

CSS 는 코딩이라고 생각합니다. 스타일들을 정리하고, 역활에 따라 구분을 짓고, 그들 사이의 관계를 설정해야 합니다. 전 CSS 정리가 매우 중요하다고 생각합니다. 웹의 각 부분들이 자신만의 잘 분리된 스타일을 갖게 됩니다. CSS 를 여러 파일들로 분리하는 것은, 정리하는데 도움이 되기도 하지만, 나름의 문제점들도 있습니다.
우리는@import 문을 사용하는 것이 그닥 좋지 않다는 것을 알고 있습니다. 왜나면 @import 하나마다 서버로 요청을 한번씩 해야 되기 때문입니다. 그리고 브라우저는 모든 스타일 파일을 다운받기 전에는 유저에게 아무것도 보여주지 않습니다. CSS 파일이 없거나, 크기가 크면, 유저가 화면에 무엇인가 보기까지 오랜 시간을 기다려야 할 수 있습니다.

Use CSS Preprocessors

CSS preprocessor 를 사용하면 위 문제를 해결할 수 있습니다. 파일들을 여러개로 분리해서 작업하고, 나중에 하나의 CSS 파일로 합쳐서 유저에게 전달할 수 있습니다. CSS preprocessor 는 변수, 중첩 블락, mixin 과 상속등 다양한 기능을 제공합니다. CSS preprocessor 를 사용해도 코딩은 일반 CSS 와 매우 비슷하게 생겼습니다. 하지만 더 관리하기가 편해집니다. SassLESSStylus. 등이 유명합니다. 다음은 LESS 의 예제입니다:
.position(@top: 0, @left: 0) {
    position: absolute;
    top: @top;
    left: @left;
    text-align: left;
    font-size: 24px;
}
.header {
    .position(20px, 30px);
    .tips {
        .position(10px, -20px);
    }
    .logo {
        .position(10px, 20px);
    }
}
는 다음과 같이 변경됩니다.
.header {
    position: absolute;
    top: 20px;
    left: 30px;
    text-align: left;
    font-size: 24px;
}
.header .tips {
    position: absolute;
    top: 10px;
    left: -20px;
    text-align: left;
    font-size: 24px;
}
.header .logo {
    position: absolute;
    top: 10px;
    left: 20px;
    text-align: left;
    font-size: 24px;
}
버튼을 위한 스타일이 있으면, 텍스트 칼러만 바꾼 버튼을 만들수도 있습니다:
.button {
    border: solid 1px #000;
    padding: 10px;
    background: #9f0;
    color: #0029FF;
}
.active-button {
    .button();
    color: #FFF;
}

효율적인 CSS

대부분의 개발자들은 CSS 의 속도(효율성) 에 대해서는 생각하지 않습니다. CSS 에 따라 페이지가 보여지는데 걸리는 시간이 더 걸리기도 덜 걸리기도 합니다. 재미있는 사실중 하나는, 브라우저가 CSS selector 를 오른쪽에서 왼쪽으로 읽습니다.
body ul li a {
    color: #F000;
    text-decoration: none;
}
… 위 코드는 매우 느립니다. 왜냐면 브라우저가 <a> 태그를 모두 찾은 후 그 부모들이 조건에 맞는지 검사하는 검사하는 식으로 동작하기 때문입니다. 또한 ID, class, tag, universal(*) 순으로 효율성이 줄어든다는 것도 알아 두시면 좋습니다. tag 로 스타일을 적용한 원소들 보다, id 로 스타일 적용된 것들이 빨리 그려지게 됩니다. 물론 모든 원소에 id 를 추가할 필요는 없지만, 이 지식이 도움이 될 때가 분명 있습니다.
ul #navigation li {
    background: #ff0232;
}
ul 을 제거하는 것이 좋습니다. 왜냐면 #navigation 은 페이지 전체에 하나밖에 없기 때문입니다.
body .content p {
    font-size: 20px;
}
.content 는 body 의 자식이기 때문에 body 가 굳이 필요 없습니다.
developers.google.com 와 css-tricks.com 에 이와 비슷한 더 많은 팁들이 있습니다.

File Size

모든 CSS 를 다운받기 전에는 유저가 아무것도 볼 수 없기 때문에, CSS 용량은 작을수록 좋습니다. 사이즈를 줄이기 위한 팁들을 소개합니다:
비슷한 스타일을 합친다:
.header {
    font-size: 24px;
}
.content {
    font-size: 24px;
}
… 다음과 같이 변경합니다:
.header, .content {
    font-size: 24px;
}
다음과 같은 코드 대신에:
.header {
    background-color: #999999;
    background-image: url(../images/header.jpg);
    background-position: top right;
}
짧게 쓸 수 있습니다:
.header {
    background: #999 url(../images/header.jpg) top right;
}
CSSOptimiser 와 Minifycss 등의 툴을 이용해서 공백을 제거하여 파일 사이즈를 줄일 수 있습니다. 서버에서 파일 사이즈를 줄여서 유저에게 작은 CSS 를 전달하는 것이 좋습니다.

CSS 파일을 안에 넣기

.css 파일을 head 안에 넣으세요. 브라우저가 그 파일들을 먼저 다운 받습니다.

JavaScript

HTTP 요청 줄이기

CSS 와 동일합니다. 서버로의 요청을 줄이세요. 대부분의 경우 자바스크립트 파일을 로딩중이라고 페이지 로딩이 중단되지는 않습니다. 페이지의 기능들이 동작을 안할 수는 있지만요.

Minify Your Code

자바스크립트의 크기를 줄여주는 라이브러리들이 있습니다. 물론 개발중에는 사용하지 마세요. 대부분의 툴들은 변수이름을 바꾸고, 코드를 한줄로 바꿔버려서 디버깅하기 매우 어렵게 됩니다.

CommonJSAMDRequireJS

자바스크립트 자체에는 모듈이라는 개념이 없습니다. 이를 해결하기 위한 라이브러리들이 있습니다. 다음 예제는 이 링크에서 가져왔습니다.
<!DOCTYPE html>
<html>
    <head>
        <title>My Sample Project</title>
        <!-- data-main attribute tells require.js to load
             scripts/main.js after require.js loads. -->
        <script data-main="scripts/main" src="scripts/require.js"></script>
    </head>
    <body>
        <h1>My Sample Project</h1>
    </body>
</html>
Inside of main.js, you can use require() to load any other scripts you need:
require(["helper/util"], function(util) {
    //This function is called when scripts/helper/util.js is loaded.
    //If util.js calls define(), then this function is not fired until
    //util's dependencies have loaded, and the util argument will hold
    //the module value for "helper/util".
});

Use Namespaces

코딩을 정리하는 주제로 얘기하자면 namespace 에 대해 빼놓을 수가 없겠죠. 자바스크립트 자체에는 namespace 개념이 없습니다. 하지만 간단히 해결할 수 있습니다. MVC 를 구현하기 위해 다음처럼 클래스를 구현했다고 합시다
var model = function() { ... };
var view = function() { ... };
var controller = function() { ... };
위 코드 그대로 사용하면, 프로젝트의 다른 파일과 겹칠 확률이 높습니다. 하지만 객체(namespace) 를 만들어서 모아둔다면, 그럴 확률이 줄어들 겁니다.
var MyAwesomeFramework = {
    model: function() { ... },
    view: function() { ... },
    controller: function() { ... }
}

Design Patterns 활용하기

이미 만들어져 있는 것을 새로 만드려고 하지마세요. 자바스크립트는 매우 많은 사람들이 쓰고 있고, 많은 것들이 만들어져 있습니다. 디자인 패턴은, 자주 발생하는 문제들에 대한 많은 사람들이 해결책을 정리해 놓은 것입니다. 남이 제시한 해결책을 사용하는 것이 도움이 될 때가 많이 있습니다. 몇가지 예를 들어 보겠습니다:

Constructor Pattern

특정 타입의 객체를 만들 때 이 패턴을 사용하세요:
var Class = function(param1, param2) {
    this.var1 = param1;
    this.var2 = param2;
}
Class.prototype = {
    method:function() {
        alert(this.var1 %2B "/" %2B this.var2);
    }
};
다음 처럼 할 수도 있습니다:
function Class(param1, param2) {
    this.var1 = param1;
    this.var2 = param2;
    this.method = function() {
        alert(param1 %2B "/" %2B param2);
    };
};

var instance = new Class("value1", "value2");

Module Pattern

모듈 패턴은 private 과 public 함수를 만들게 해줍니다. 예를들어 다음 코드에서 _index 와 privateMethod 는 private이고 increment 와 getIndex 은 public 입니다.
var Module = (function() {
    var _index = 0;
    var privateMethod = function() {
        return _index * 10;
    }
    return {
        increment: function() {
            _index %2B= 1;
        },
        getIndex: function() {
            return _index;
        }
    };
})();

Observer Pattern

이벤트를 받거나 전달할 때, 이 패턴을 보게됩니다. 옵저버들은 다른 특정 객체에 관심을 갖고 있습니다. 이벤트가 발생하면 옵저버들이 이벤트에 대해 알게 됩니다. 아래 예제 는 어떻게 Users 객체에 옵저버를 추가할 수 있는지 보여줍니다:
var Users = {
    list: [],
    listeners: {},
    add: function(name) {
        this.list.push({name: name});
        this.dispatch("user-added");
    },
    on: function(eventName, listener) {
        if(!this.listeners[eventName]) this.listeners[eventName] = [];
        this.listeners[eventName].push(listener);
    },
    dispatch: function(eventName) {
        if(this.listeners[eventName]) {
            for(var i=0; i<this.listeners[eventName].length; i%2B%2B) {
                this.listeners[eventName][i](this);
            }
        }
    },
    numOfAddedUsers: function() {
        return this.list.length;
    }
}

Users.on("user-added", function() {
    alert(Users.numOfAddedUsers());
});

Users.add("Krasimir");
Users.add("Tsonev");

Function Chaining Pattern

이 패턴은 public 인터페이스를 정리하는데 도움이 되는 패턴입니다. 코드의 가독성을 높일 수 있습니다:
var User = {
    profile: {},
    name: function(value) {
        this.profile.name = value;
        return this;
    },
    job: function(value) {
        this.profile.job = value;
        return this;
    },
    getProfile: function() {
        return this.profile;
    }
};

var profile = User.name("Krasimir Tsonev").job("web developer").getProfile();
console.log(profile);
JavaScript 의 패턴에 관한 아주 좋은 책 을 추천합니다.

Assets-Pack

글이 막바지에 다다르고 있습니다. 서버에서 CSS 와 JavaScript 를 관리하는 방법에 대한 얘기를 하고 싶습니다. 파일을 합치고, 사이즈를 줄이고(minification), 컴파일(preprocessor 등) 하는 것을 서버 코드의 일부로 구현하는 경우가 많이 있습니다.
제가 최근에 작업한 프로젝트에서, assets-pack 라는 툴을 사용했습니다. 매우 유용한 것 같아, 제가 어떻게 이 툴을 이용했는지 자세히 설명드리고자 합니다. 이 라이브러리는 개발중에만 사용하도록 만들어졌습니다.
기본적으로 여러 리소스 파일들(CSS, JS) 이 변경되면, 이 툴이 알아서, 모든 파일을 하나의 파일로 합쳐줍니다. 파일을 하나만 전송해도 되니, 속도가 빨라집니다. 이 툴 이외에 서버에 다른 기능을 추가할 필요가 없기 때문에 개발하는 동안 편리하게 사용할 수 있습니다.
[assets-pack][23] 설치방법입니다.

Installation

이 툴은 Nodejs 를 사용합니다. 없으신 분들은 nodejs.org/download 에서 파일을 받으세요. 그리고 다음과 같이 합니다:
npm install -g assetspack

Usage

Command Line

이 툴은 JSON 설정파일을 사용합니다.
assets.json 파일을 만들고 같은 디렉토리에서 다음 명령어를 실행합니다:
assetspack
설정파일의 이름이 다르거나 다른 디렉토리에 있다면 다음과 같이 하세요:
assetspack --config [path to json file]

In Code

var AssetsPack = require("assetspack");
var config = [
    {
        type: "css",
        watch: ["css/src"],
        output: "tests/packed/styles.css",
        minify: true,
        exclude: ["custom.css"]
    }
];
var pack = new AssetsPack(config, function() {
    console.log("AssetsPack is watching");
});
pack.onPack(function() {
    console.log("AssetsPack did the job");
});

Configuration

설정파일은 다음과 같이 생겼습니다:
[
    (asset object),
    (asset object),
    (asset object),
    ...
]

Asset Object

기본적인 asset object 는 다음과 같이 생겼습니다:
{
    type: (file type /string, could be css, js or less for example),
    watch: (directory or directories for watching /string or array of strings/),
    pack: (directory or directories for packing /string or array of strings/. ),
    output: (path to output file /string/),
    minify: /boolean/,
    exclude: (array of file names)
}
pack property 가 반드시 필요한 것은 아닙니다. 생략하면 watch 와 같은 값을 갖게 됩니다. minify 는 기본값이 false 입니다.
몇가지 예입니다:

Packing CSS

{
    type: "css",
    watch: ["tests/data/css", "tests/data/css2"],
    pack: ["tests/data/css", "tests/data/css2"],
    output: "tests/packed/styles.css",
    minify: true,
    exclude: ["header.css"]
}

Packing JavaScript

{
    type: "js",
    watch: "tests/data/js",
    pack: ["tests/data/js"],
    output: "tests/packed/scripts.js",
    minify: true,
    exclude: ["A.js"]
}

Packing .less Files

.less 압축(packing)은 조금 다릅니다. less 파일에 대해서는 pack property 가 필수입니다. 다른 모든 .less 파일을 import 하여야 하고 exclude property 는 제공되지 않습니다.
{
    type: "less",
    watch: ["tests/data/less"],
    pack: "tests/data/less/index.less",
    output: "tests/packed/styles-less.css",
    minify: true
}
문제가 있으면 Github 저장소의 tests/packing-less.spec.js 를 보세요.

Packing Other File Formats

assets-pack 툴을 다른 파일 포맷에도 사용할 수 있습니다. HTML 을 합쳐서 하나로 만들 수도 있습니다:
{
    type: "html",
    watch: ["tests/data/tpl"],
    output: "tests/packed/template.html",
    exclude: ["admin.html"]
}
다만 이 경우 minification 은 지원되지 않습니다.

원본출처: http://code.tutsplus.com/articles/web-assets-tips-for-better-organization-and-performance--net-33950
번역출처: http://codeflow.co.kr/question/365/%EC%9B%B9%EC%82%AC%EC%9D%B4%ED%8A%B8-%EC%86%8D%EB%8F%84-%EA%B0%9C%EC%84%A0%EC%9D%84-%EC%9C%84%ED%95%9C-%ED%8C%81%EB%93%A4/