« Previous : 1 : 2 : 3 : 4 : 5 : 6 : ... 8 : Next »

대량 DML 로 인한 gc 대기 잡기

갑자기 DB가 Hang이 걸리거나 WAS에서 큐가 마구 쌓일 때, 일단 대기를 살펴보자.

잽싸게

create table pnc$active_ses as
select * from v$active_session_history


를 해놓고 초단위로 IO, User, Cluster, Concurrency , Other등의 Wait Class 별로 발생빈도를 측정한다. 그러면 어떤 순서로 대기가 발생했는지 알 수 있고 DB에서 무슨 일이 벌어졌는지 실마리를 잡을 수 있다.

어제 대량 DML 로 인한 gc 대기를 그렇게 잡았다.

위의 방식으로 살펴보니 instance 레벨에서 Cluster 대기가 엄청 발생하고, 그 후에 Concurrency 가 발생했다. 주로 gc cr current block-2way 와 gc cr request 가 먼저 왕창 생기고 그 후에 latch: cache buffer chains 가 발생했는데...

http://wiki.ex-em.com/index.php/Gc_cr/current_block_2-way 참고
http://wiki.ex-em.com/index.php/Gc_cr/current_request 참고

current block 인 것으로 보아 갑자기 대량 DML이 발생한 것 같아서 여기저기 수소문해보니 그런 일이 있었다. 업무적으로 이걸 막기는 힘든 상황.. 마침 gc cr block lost 도 많이 보여서 UDP 패킷 사이즈를 체크.

* gc cr/current request - Tuning
 
비효율적인 네트워크 설정
 - Gigabit Ethernet Interconnect에서는 UDP(User Diagram Protocol) 사용
 - 확인 방법 :
 SQL> oradebug setmypid
 SQL> oradebug ipc
 SQL> oradebug tracefile_name
 
[예제]
PCSDB1:SQL>oradebug setmypid
Statement processed.
PCSDB1:SQL>oradebug ipc
Information written to trace file.
PCSDB1:SQL>oradebug tracefile_name
/cs_orahome/oracle/10.2.0/admin/PCSDB1_csdb1/udump/pcsdb1_ora_11771.trc
PCSDB1:SQL>exit
Disconnected from Oracle Database 10g Enterprise Edition Release 10.2.0.3.0 - 64bit Production
With the Partitioning, Real Application Clusters, OLAP and Data Mining Scoring Engine options
[PCSDB1] /cs_orahome/oracle/10.2.0/rdbms/admin> vi /cs_orahome/oracle/10.2.0/admin/PCSDB1_csdb1/udump/pcsdb1_ora_11771.trc
 
* 결과
locked 1
blocked 0
timed wait receives 0
admno 0xfe8bc48 admport:
SSKGXPT 0x217880 flags SSKGXPT_READPENDING      socket no 8     IP 10.1.1.1     UDP 58379
context timestamp 0
        no ports
 
 - RAC Interconnect 성능과 가장 연관 있는 것은 UDP Buffer Size.
 - 오라클 권장 UDP buffer size = 256K
 - UDP Buffer Size가 작으면 패킷유실(Packet loss) 현상이 발생. (gc cr/current block lost 발생)
 
 * 패킷 유실 확인
 [PCSDB1] /cs_orahome/oracle> netstat -s -p udp
udp:
        57726 incomplete headers
        4795 bad checksums
        52931 socket overflows
 
 
# 아래 metalink 문서에서는 HP-UX서버에서는 UDP 튜닝이 안되는걸로 확인됨
 1.문서변경 : Reference site확인결과
 UDP Send buffer size는 64K로 Fix되어 다른 값으로 변경 할 수 없고
 UDP Receive buffer size는 ndd명령으로 변경가능함
 
 2. Reference Site size
 - 메리츠 증권 : 1M
 - 신한은행 : 2G
 - SKT : 1M

OS 커널 파라미터를 UDP 패킷 버퍼 1M 로 변경하고 나니 문제 해결~~~

오늘의 교훈

뭔 일이 생기면 일단 active_session_history 를 덤프떠놓고 세심하게 분석해보자!!

Posted by maceo

12 18, 2008 19:46 12 18, 2008 19:46
, , ,
Response
No Trackback , No Comment
RSS :
http://merritt.co.kr/tt/rss/response/122

SGA 가 춤을 출 때

SGA에서 shared pool 과 buffer cache 가 커졌다 작아졌다 난리부르스를 출 때...

확인은

select *
from (
    select INST_ID,
      COMPONENT,
      OPER_TYPE,
      OPER_MODE,
      PARAMETER,
      INITIAL_SIZE,
      TARGET_SIZE,
      FINAL_SIZE,
      (final_size-initial_size)/1024/1024 as delta_mb,
      STATUS,
      START_TIME,
      END_TIME
    from GV$SGA_RESIZE_OPS
    where inst_id=1
    order by inst_id, END_TIME desc)
where rownum <= 50

만약 buffer cache 가 GROW 하고 shared pool이 줄어들 때 shared pool 쪽에서 library cache pin 이 일어날 수 있다. 잘못하면 이거 안풀리고 DB가 Hang까지 갈 수 있다.

왜 이런 일이 일어날까? 10g 에서부터 buffer cache 가 커지면 shared pool 의 영역을 일부 뺏어와서 쓰게 된다. 이 영역을 KGH: NO ACCESS 영역이라고 한다.

Resizing the SGA « Oracle Scratchpad 참고

오라클은 KGH: NO ACCESS 영역을 찜해놓고, 그 안에 있는 LCO를 바로 clean out 하지는 않는다. 그렇게 하기도 어렵고 대단히 무리한 CPU Operation 이 될 수 있기 때문이다. 그래서 영역을 일단 찜하고 그 안에 있는 LCO들이 unpin되면 그 영역을 buffer cache로 점진적으로 가져온다. 이럴 때 위의 쿼리에서 OPER_MODE 가 DEFFERED 로 찍힌다.

그러면 왜 library cache pin이 일어날까?

이건 추측인데... 메모리를 KGH: NO ACCESS 로 표시할 때 그 안에 들어가있는 LCO 들에 대해서 어떤 플래그를 달아야 하는데, 그 때 pin 이 걸리는게 아닐까 싶다. 확인할 길은 없다. 물론 영역 크기가 크면 자주 호출되는 LCO 가 걸려들 가능성이 높을거고 그때 pin 이 걸리겠지.


또 하나 관찰한 흥미로운 증상은, SGA Max > SGA Target 일 때 SGA resize가 더욱 빈번하게 일어난다는 거다. 거의 1~2분에 한번씩 일어나서 오라클 버그로 의심했으나

Simple Is Beautiful | SHARED POOL中KGH: NOACCESS占用大量内存的问题分析  참고

우리가 쓰는 10.2.0.3 에서는 패치가 되었다고 Oracle SR 결과 확인이 되었다. 그래서 궁여지책으로 SGA Max = SGA Target 으로 맞추고 나니 증상이 훨씬 덜해졌다. 이건 또 왜 이런걸까??

그리고.. SGA Max > SGA Target 으로 설정해놓고 몇G의 여유분을 놔둔다 하더라도 쎈 배치가 돌거나 어떤 일이 생기면 SGA Target을 넘어서서까지 메모리를 할당을 하는 것 같다. PGA도 pga_aggregate_target을 넘어서서 할당을 하는데 (v$pga_target_advice뷰의 over allocation참조) SGA Target도 마찬가지인듯. 그리고 Target을 넘어서서 메모리를 사용하고 나면 자동적으로 Target까지 shrink 를 시도하는게 아닌가 한다.

살펴보니 추측투성이네~

결론은...

ASSM쓸거면 SGA Max = SGA Target으로 해놓자.

이다.

Posted by maceo

12 18, 2008 19:30 12 18, 2008 19:30
, ,
Response
No Trackback , No Comment
RSS :
http://merritt.co.kr/tt/rss/response/121

중급 개발자들이 보아야 할 오라클 책

오늘 중급개발자로 부터 오라클을 잘사용할수 있도록 3종류의 책을 추천 해달라는 요청을 받았다.
"오라클을 7~8년 정도 사용하였지만 3~4년 전부터 SQL 실력이 늘지않는다." 가 추천을 요청한 사유이다.
3종류의 책은 다음과 같다.
1. SQL 을 자유자재로 구사할수 있는 능력을 기르는 책
   참고로 위의 책은 튜닝책을 이야기 하는것이 아니다.
   원하는 답을 내기위한 효율적인 SQL 활용법을 가르쳐주는 책을 이야기 하는것이다.  
2. SQL 튜닝책
3. PL/SQL 튜닝책

물론 단한권으로 1,2,3 번을 어느정도 기본적인 사항들을 커버하는 책이 있다.
바로 Tomas KyteEffective Oracle by Design 이다.
하지만 위의 책을 정복 한다고 해도 1,2,3 번중 어느것도 Master 가 될수는 없다.
왜냐하면 한권의 책으로는 깊이의 한계와 페이지수의 한계를 넘지못하는 것이다.
마찬가지로 국내서적중에서 깊이있는 책이 존재하지만 1,2,3번을 모두다 다루려고 하다보니 대부분 깊이
들어가지 못하고 소개정도에 그치고 있다.  

해당 개발자는 다행스럽게도 영문서적도 상관없다고 하였다.
또한 각권을 독파하면 바로 효과를 볼수 있는 책을 원했다.
다시 말하면 투자시간 대비 효율이 좋은 책을 원하는 것이다.
어떤책이 투자시간 대비 효율이 좋은 책인지 이해를 돕기위해 아래의 그림을 보자.

사용자 삽입 이미지












위의 곡선은 필자가 생각하는 실력 상승곡선이다.
검은 화살표 3개를 보면 조금만 노력을 하면 실력이 급격히 향상되는것을 알수있다.
이것은 초급 중급 단계에서는 조금만 노력해도 실력향상의 폭이 크다는 것을 나타낸다.
하지만 상대적으로 빨강화살표 부분부터는 노력을 많이해도 실력이 별로 늘지 않음을 알수 있다.
다시말하면 고급(고수의 세계)단계에서는 책 1~2 권 읽는다고 해서 실력이 그다지 늘지않는다.    
누구도 실력 상승곡선의 예외일수 없다.
오늘 추천하는 책 3권은 오라클 개발및 튜닝 관점에서 빨강화살표 부분까지 가장 빨리 도달할수있게
도와주는(효율이 좋은) 책이라 할수 있다.  
개발자들이 읽어야할 책과 DBA, 컨설턴트들이 보아야 할 책은 엄연히 다르고 생각한다.
개발자 입장에서 일단 후보가 될수있는 책을 생각하니 7~8권 정도 생각이 났지만 몇시간의 장고 끝에
아래의 3권을 추천 하였다.

1.SQL 을 자유자재로 구사할수 있는 책 :
   Mastering Oracle SQL 2nd Edition (저자:Sanjay Mishra & Alan Beauieu)

2.SQL 튜닝책 :
   SQL Tuning (저자:Dan Tow)

3.PL/SQL 튜닝책 :
   Mastering Oracle PL/SQL (저자:Cornor McDonald)


각각의 장점을 설명하면 아래와 같다.

1.Mastering Oracle SQL 2nd Edition (저자:Sanjay Mishra & Alan Beauieu)
  말이 필요없는 SQL 활용의 대가인 Alan Beauieu 의 작품이다.
  중급개발자라면 1,2장은 넘기면서 볼수 있지만 3장부터는 신경을 써야한다.
  모든 스크립트는 온라인으로 Down Load가 가능하다.
  자세한 내용은 필자의 서평을 참조하기바란다.

2.SQL Tuning (저자:Dan Tow)
  Oracle 뿐아니라 DB2, SQL Server 의 SQL 튜닝을 모조리 커버하는 놀라운 책 이다.
  여러분이 만약 업무를 모르는 상태에서 SQL 튜닝 요청을 받았다고 가정하고 해당 SQL 의 FROM 절에
  테이블이 8~10 개정도 있다고 할때 조인순서및 조인방법을 쉽고 빠르게 확정할수 있겠는가?
  국내외를 막론하고 조인순서및 조인방법에 대하여 이책만큼 명확한 방법론을 제시하는 책은 없었다.
  Dan Tow 의 Diagram-Based 방법론을 익힌다면 SQL 튜닝에 대해서는 더이상 걱정이 없을것이다.
  Method-R 방법론을 집대성한 Cary Milsap 이 적극 추천한 책이기도 하다.

3.Mastering Oracle PL/SQL (저자:Cornor McDonald)
  PL/SQL 문법이나 활용서적이 아니라 튜닝 책이다.
  마지막 순간까지 이책과 Steven Feuerstein 의 Oracle PL/SQL Best Practices를 두고 고민 하였다.
  하지만  Steven Feuerstein 의 책은 100% PL/SQL 튜닝 책이 아니고 PL/SQL 의 효율적인 활용에 대해서
   50% 정도 언급을 하고 있기 때문에 제외 시켰다.
  Cornor McDonald의 책은 100% 튜닝관점에서 집필된 책이다.
  모든 소스 코드가 온라인에 공개되어 있으므로 튜닝과정을 따라가기가 매우쉽다.  
  예를 하나만 들면
 "같은 답을 내는 2가지의 PL/SQL 이 있고 구현방법이 다를때 redo 양이 적게 발생하는 것이 무엇인지?"
  등의 상당히 재미있는 주제가 많다.  
  기본(1,2번 책)을 익히고 이책을 정복한다면 이책의 제목처럼 PL/SQL 튜닝에 대해서는 Master 가 될것이다.

모든 중급개발자및 DBA들에게 위의 3가지 책을 자신있게 추천한다.
만약 컨설턴트들이 위의 책들을 보지 못했다면 실수라고 말하고 싶다.
이런 종류의 책이 국내에는 없다는점이 아쉬울 뿐이다.
최근에 환율이 장난이 아니고 난위도가 어느정도 있는책이므로 중간에 포기할 생각이라면 원서를
구입하지말고 E-Book 등을 활용하기 바란다.
참고로 3번책을 제외하면 E-Book 을 구할수 있다.(방법은 각자 알아서...)

편집후기 :
혹시 여러분들이 1,2,3 번에 대해여 다른책을 추천한다면?
물론 효율(실력 상승곡선)을 염두에 두어야 한다.
여러분들의 의견을 듣고 싶다.  


-----

일단 위의 3권을 보시는데만 6개월은 족히 걸릴거 같습니다.
제가 서평을 3~4 개월 마다 올리므로 그때 참조하시는 것도하나의 방법이 되겟습니다.
참고로 제가 고민 했던 7~8 권은 아래와 같습니다.
1.SQL 을 자유자재로 구사할수 있는 책
SQL COOK BOOK
The Art of SQL
대용량 데이터베이스 솔루션 2권
2.SQL 튜닝책:
Oracle SQL High-Performance Tuning (2/E)
대용량 데이터 베이스 솔루션1 OLD 버젼
3.PL/SQL 튜닝책
Oracle PL/SQL Best Practices
Expert Oracle PL/SQL

Posted by maceo

11 12, 2008 13:16 11 12, 2008 13:16
Response
No Trackback , No Comment
RSS :
http://merritt.co.kr/tt/rss/response/119

first_rows_n

first_rows_n

Filed under: CBO, Execution plans, Hints, Performance, Tuning — Jonathan Lewis @ 1:05 pm UTC Nov 11,2008

When it comes to setting the optimizer_mode parameter you often hear people say that first_rows_N (for one of the legal values of N) should be used for OLTP systems and all_rows should be used for decision support and data warehouse systems.

There is an element of truth in the statement - but it’s really a hangover from the early days of CBO, and remembrance of  the old first_rows optimizer mode (** See footnote).

Prior to the introduction of parameters like optimizer_index_cost_adj (Oracle 8), and system statistics (Oracle 9 - but optional until 10g), the cost based optimizer often seemed too keen to use execution plans that involved tablescans and hash joins - which could be most unsuitable in an OLTP system.

This problem appeared because the optimizer did not attempt to distinguish between the potential performance of a “db file sequential read” (single block read) compared to a “db file scattered read” (multiblock read).  Setting the optimizer_mode to first_rows introduced some simple rules that (to a large extent) stopped the optimizer from using tablescans unless there were no alternatives.

In 9i, however, first_rows_N optimisation is geared towards using arithmetic to make decisions about avoiding tablescans. You can see the results of this in some of the odd numbers that appear in execution plans as the optimizer works out whether, for example, it is better to use an index to pick up 10 rows from a table, or whether running 2.3% of a tablescan (which would probably get the first 10 rows) is the cheaper option.

Think about what this means - first_rows_N is trying to work out the cheapest way of collecting the first N rows of the result set, whereas all_rows is trying to work out the cheapest way of collecting all the rows of the result set.

But when you are running an OLTP system a huge fraction of typical end-user queries will be returning just a handful of rows - and if you have a query that’s only supposed to return 5 or 6 rows (say), then first_rows_10 actually means “all the rows I expect”, i.e. all_rows. If the optimizer works correctly, then both options should produce the same execution plan.

Of course, some end-user queries (even in an OLTP system) are reports, and for reports you don’t want to generate a plan that’s optimised to return the first few lines so, again, you’re really interested in all_rows optimisation.

Your conclusion should therefore be: for OLTP systems, first_rows_N ought to be irrelevant. You should be using all_rows - unless you can demonstrate that this introduces a large number of execution plans that have to be fixed individually because of some unexpected side effects (Again, though, see footnote).

So why would we ever need first_rows_N ?

Think Google, or Amazon, or any of the “forum” software suppliers.  There are systems which are effectively ad hoc query systems where users can supply query predicates that could return a lot of data - but the code limits the amount of data sent back to the user to “page at a time”. Think “web-based reporting system”, and you’ve identified the basic rationale for first_rows_N.

Even for the web-base reporting system, though, you may still be able to run under all_rows from from 10g onwards. In 10g when the optimizer sees predicates like “rownum <= 35″ it will behave as if you had included the hint /*+ first_rows(35) */ in the query.

As a closing thought: even if you think that first_rows_N may be the correct logical and strategic choice for your system, remember that it hasn’t been “field-tested” to the extent that all_rows has been tested.

There are bugs (or side effects) in the optimizer that make their first appearance only in the strange circumstances of “real-world” applications - and it’s a good bet that such oddities are found (and probably fixed) faster in the all_rows environment than in the first_rows_N environment simply because far more systems use all_rows.

Even when first_rows_N seems to be the right choice, you could decide to stick with all_rows and adjust critical statements with a /*+ first_rows(N) */ hint.

Footnote: If you still have any systems running 9i or later and using first_rows, you should be planning to change the optimizer_mode when you next go through a full test cycle. As the manuals point out (9.2 Performance Tuning Guide and Reference p1-12): first_rows is available for backward compatibility and plan stability”.

Backward compatibility is important, and until you enable system statistics, clean up various other optimizer related parameters, sort out your statistics collection strategy, and clean out a load of redundant hints and SQL hacks, you may find that changing the optimizer_mode introduces as many problems as it solves - and that’s why this is a change that needs a lot of testing

Posted by maceo

11 11, 2008 23:56 11 11, 2008 23:56
Response
No Trackback , No Comment
RSS :
http://merritt.co.kr/tt/rss/response/118

PX Buffer (from OracleScratchPad)

http://jonathanlewis.wordpress.com/2008/11/05/px-buffer/

PX Buffer

Filed under: Uncategorized — Jonathan Lewis @ 7:11 pm UTC Nov 5,2008

Here’s a surprising anomaly that showed up in a question on the OTN forums a little while ago. Consider a simple query that uses a hash join between two tables.


select
	/*+
		ordered
		use_hash(t2)
		parallel(t1 2)
		parallel(t2 2)
		pq_distribute(t2 hash hash)
	*/
	t1.padding,
	t2.padding
from 	t1, t2
where	t2.n1 = t1.n1
and	t2.small_vc = t1.small_vc
;

When it runs serially the join completes in memory and the only I/O you see comes from the two tablescans. When the query runs parallel something causes a spill to the temporary tablespace. Here’s the code to build the tables, followed by the execution plans from 9.2.0.8 and 10.2.0.3:


alter session set workarea_size_policy = manual;
alter session set hash_area_size = 10495760;

create table t1
nologging		-- adjust as necessary
as
with generator as (
	select	--+ materialize
		rownum 	id
	from	all_objects
	where	rownum <= 3000
)
select
	rownum				n1,
	lpad(rownum,6,'0')		small_vc,
	lpad(rownum,200,'0')		padding
from
	generator	v1,
	generator	v2
where
	rownum <= 10000
;

create table t2
nologging		-- adjust as necessary
as
with generator as (
	select	--+ materialize
		rownum 	id
	from	all_objects
	where	rownum <= 3000
)
select
	1 + mod(rownum,10000)			n1,
	lpad(1 + mod(rownum,10000),6,'0')	small_vc,
	lpad(rownum,500,'0')			padding
from
	generator	v1,
	generator	v2
where
	rownum <= 20000
;

-- gather statistics here

Execution plan: 9.2.0.8
-------------------------------------------------------------------
| Id| Operation          | Name| Rows |  TQ   |IN-OUT| PQ Distrib |
-------------------------------------------------------------------
|  0| SELECT STATEMENT   |     |    2 |       |      |            |
|* 1|  HASH JOIN         |     |    2 | 98,02 | P->S | QC (RAND)  |
|  2|   TABLE ACCESS FULL| T1  |10325 | 98,00 | P->P | HASH       |
|  3|   TABLE ACCESS FULL| T2  |20212 | 98,01 | P->P | HASH       |
------------------------------------------------------------------- 

Execution plan 10.2.0.3
--------------------------------------------------------------------------------
| Id | Operation               | Name     | Rows  |    TQ  |IN-OUT| PQ Distrib |
--------------------------------------------------------------------------------
|  0 | SELECT STATEMENT        |          |     2 |        |      |            |
|  1 |  PX COORDINATOR         |          |       |        |      |            |
|  2 |   PX SEND QC (RANDOM)   | :TQ10002 |     2 |  Q1,02 | P->S | QC (RAND)  |
|* 3 |    HASH JOIN BUFFERED   |          |     2 |  Q1,02 | PCWP |            |
|  4 |     PX RECEIVE          |          | 10000 |  Q1,02 | PCWP |            |
|  5 |      PX SEND HASH       | :TQ10000 | 10000 |  Q1,00 | P->P | HASH       |
|  6 |       PX BLOCK ITERATOR |          | 10000 |  Q1,00 | PCWC |            |
|  7 |        TABLE ACCESS FULL| T1       | 10000 |  Q1,00 | PCWP |            |
|  8 |     PX RECEIVE          |          | 20000 |  Q1,02 | PCWP |            |
|  9 |      PX SEND HASH       | :TQ10001 | 20000 |  Q1,01 | P->P | HASH       |
| 10 |       PX BLOCK ITERATOR |          | 20000 |  Q1,01 | PCWC |            |
| 11 |        TABLE ACCESS FULL| T2       | 20000 |  Q1,01 | PCWP |            |
-------------------------------------------------------------------------------- 

I’ve created table t1 with 10,000 rows and a (unique) column n1 that ranges from 1 to 10,000. Table t2 then has 20,000 rows, again with an n1 column that ranges from 1 and 10,000 in this case with two rows per value. When you join the two tables on their n1 columns, the result set will have 20,000 rows.

The table-creation code is more complex than it really needs to be, but I wrote it that way in case I needed to create a few million rows in each table.

You will notice that I have switched to manual workarea management so that I can set the hash_area_size (rather than letting Oracle pick a dynamic limit based on the pga_aggregate_target and current PGA usage). This is simply to make the test case reproducible. I’ve also included the pq_distribute() hint in the query to tell the optimizer to use the hash/hash distribution rather than broadcasting the first table (which could be forced by the hint /*+ pq_distribute(t2, broadcast, none) */). In my example, the optimizer took the hash/hash option automatically and I have just included the hint for reasons of reproducibility, and to demonstrate the hint syntax.

Mechanisms:

Checking the execution plans, we can build the following picture:

One set of PX slaves scans t1 (referenced as para_1 in the picture) and distributes the data by hashing (on the n1 column) to the second set of slaves by writing virtual table (or table queue) Q0, then the same set of slaves scans table t2 (para_2) and distributes the data in the same way, using virtual table Q1.

The second set joins the two virtual tables by reading Q0 to build the hash table then probing the hash table with each row from Q1 as it arrives, passing each join result to the query coordinator by writing to virtual table Q2.

Data Volume

So, I’ve got 10,000 rows of about 200 bytes each for a total of about 2MB, and a table of 20,000 rows of about 500 bytes each for a total of about 10MB, and a join result of 20,000 rows of about 700 bytes for a total of 14MB. Since we are running parallel 2, we might expect each PX slave to see about 5,000 rows (1 MB) from t1, 10,000 rows (5MB) and 10,000 rows (7MB) of result.

Since we set the hash_area_size to 10MB, we seem to have enough memory to hold the hash table completely in memory. We could even hold the hash table and all the data we use from t2 in memory – we could even hold the hash table and the result set in memory. So why to we see writes and reads to the temporary tablespace?

Analysis

The first thing to do, of course, is to find out where that I/O comes from. Enabling events 10046 (wait tracing) and 10104 (hash join tracing) is a good starting point.

The 10104 trace (9i version shown below) starts like this (the line numbers are my addition to the normal trace):


*** HASH JOIN STATISTICS (INITIALIZATION) ***
 1 Original memory: 10495760
 2 Memory after all overhead: 10342503
 3 Memory for slots: 9691136
 4 Calculated overhead for partitions and row/slot managers: 651367
 5 Hash-join fanout: 8
 6 Number of partitions: 9
 7 Number of slots: 13
 8 Multiblock IO: 91
 9 Block size(KB): 8
10 Cluster (slot) size(KB): 728
11 Minimum number of bytes per block: 8160
12 Bit vector memory allocation(KB): 512
13 Per partition bit vector length(KB): 64
14 Maximum possible row length: 1047
15 Estimated build size (KB): 2636
16 Estimated Row Length (includes overhead): 540
17 # Immutable Flags:
18   BUFFER the output of the join for Parallel Query

The trace reports 10MB of memory available for the operating the hash join (line 1).

The optimizer has pre-empted the possibility of the hash table spilling to disc by allowing for 9 partitions (line 6) although it talks about the “fanout” being 8 (line 5). Now the number of partitions is “always” a power of 2 and “always” matches the fanout – so what’s that extra partition for ?

We see that the optimizer has decided to use units of 91 blocks for any I/O to the temporary tablespace (line 8 ) – this translates into a size of 728KB (line 10) as the unit I/O (“cluster”, or “slot”) size. (In terms of parameters, this setting can be dictated by the parameter hash_multiblock_io_count - which is hidden in newer versions of Oracle).

This “slot” size has a very important impact on the efficiency of the hash join. To start with each partition needs minimum of one slot to hold data. This means that the minimum size in memory of the hash table will have to be at least 5.6MB (728KB * 8 ) because the hash table is going to be partitioned 8 ways. We can see this a little further down the trace file, as the hash table build completes.



*** HASH JOIN BUILD HASH TABLE (PHASE 1) ***
Total number of partitions: 8
Number of partitions which could fit in memory: 8
Number of partitions left in memory: 8
Total number of slots in in-memory partitions: 8
Total number of rows in in-memory partitions: 5029
   (used as preliminary number of buckets in hash table)
Estimated max # of build rows that can fit in avail memory: 77792
### Partition Distribution ###
Partition:0    rows:577        clusters:1      slots:1      kept=1
Partition:1    rows:605        clusters:1      slots:1      kept=1
Partition:2    rows:634        clusters:1      slots:1      kept=1
Partition:3    rows:630        clusters:1      slots:1      kept=1
Partition:4    rows:635        clusters:1      slots:1      kept=1
Partition:5    rows:645        clusters:1      slots:1      kept=1
Partition:6    rows:665        clusters:1      slots:1      kept=1
Partition:7    rows:638        clusters:1      slots:1      kept=1
>

This means the entire hash table has been built completely in memory (kept = 1 for every partition) with a huge amount of space to spare. We have 5,029 rows in memory, and could handle an estimated 77,792 in the space allocated.

After the build completes, the 10046 trace details show the process reading data from the next virtual queue then, a bit further down the file, we get the following extra clues from the 10104 trace:


kxhfWrite: hash-join is spilling to disk
kxhfWrite: Writing dba=23177 slot=8 part=8
...
qerhjFetch: PQ Partition rows:10058      clusters:8      in-memory slots      4

We have been dumping partition 8 to disc – but the hash table is in partitions 0 to 7, so what are we dumping?

There are only two possibilities – either Oracle is copying the incoming probe table to partition 8 before it does the join, or it is writing the result of the join to partition 8 before re-reading it and forwarding it to the query coordinator. A couple of simple checks (e.g. adjust the n1 values in t2 to ensure that the join returns no data) shows that it’s the result set that is being dumped.

Looking back at lines 17 and 18 of the trace file, we can see that this was dictated by the “immutable flags” which were set to “BUFFER the output of the join for Parallel Query”.

Why ?

This behaviour is astonishing – there is clearly no need to do things this way; it’s a total waste of effort and time. Why write, re-read, then forward the result set when it could simply be sent as it was created ? Alternatively, why doesn’t the PX slave write its virtual table (Q2) to the temporary tablespace and let the Query Co-ordinator read it ? The code seems to be doing something quite bizarre.

Here’s my guess. First, we’re looking at a special case; secondly, in the more general case a different read/write strategy could become quite complex – especially when the query started to run across multiple RAC instances.

Generically, a single DFO (data flow operation) may involve far more than two layers of parallel execution; but a single DFO can use only two sets of parallel execution slaves. So if you have a parallel execution plan had three “layers” of operations, what happens: slave set one does the first operation, slave set two does the second operation, and slave set one does the third operation.

But slave set two (in the middle) may have some output ready for the third operation (slave set one) before the first operation (also slave set one) has completed – which means slave set one won’t be in place to receive the output. If this happens, slave set two has to do something with the output – which explains why it has to have some sort of buffer operation.

My guess is that it is this buffering operation that appears in our example, even though in this particular case it is “obviously” not needed.

If you accept the need for some sort of buffering mechanism, the next question is, why not make one layer of slaves write to the temporary tablespace and the next layer read from it. This could cut out a huge amount of messaging between two layers of slaves – the only information that would have to be sent through the SGA is a list of which blocks in the temporary segment should be read by which slaves.

This sounds reasonable – but the code needed to handle the generic case may be sufficiently complicated that the cost/risk/benefit analysis says that it’s not worth doing.

Think about a simple cycle where slave set 1 writes to the temporary tablespace for slave set 2 to read, and slave set 2 writes to the temporary tablespace for slave set 1. We don’t need a more complicated example because slave set 1 is not going to start reading for the third operation until after it has finished the first operation, so a double layer is a complex as things can get.

But, even though we have a limit of two layers, think about the number of communication channels. If we are running degree N there are N slaves in slave set 1 which need to distribute data across N slaves in slave set 2, so we need to isolate N*N channels of communication. Then you have to double that number because you could have two layers of communication live at once.

There is Metalink note somewhere that points out the amount of memory you should allocate for PX messaging in the large pool is something like 3 * parallel_execution_message_size * parallel_max_servers * parallel_max_servers / 4 (which allows 3 buffer pages per possible channel when one query runs at degree parallel_max_servers / 2).

If you try to switch this messaging to the temporary tablespace, would you allocate one extent for each channel – probably not, as that could need a huge, and sparsely used, temporary tabelspace.

On the other hand if you tried something like one private segment per query, or per operation per query, you then have to write code that allows slaves in the same layer to negotiate with each other to ensure that they don’t overwrite each others blocks in that extent. This is the efficient, but complex, solution. And when you realise that this could include communication across a RAC interconnect, you can appreciate that Oracle Corp. might avoid rewriting the code unless it proved to be absolutely necessary for a number of really large customers.

So parallel queries may do far more I/O to the temporary tablespace than you expect (and this is just one of the reasons). The “HASH JOIN BUFFERED” operation that becomes visible in 10g (see line 3 of the plan above) may tell you where some of that I/O is coming from. (The same action appears in all versions I tested from 8i to 11g, by the way – it’s just that 10g is the first version to report the operation).

Damage limitation

If you do find that your parallel hash joins are working harder than you expect, check the plan, and check the distribution. It isn’t well known, but you can make a huge difference to the resource consumption by controlling exactly what Oracle does with hash joins. In this particular case, switching to a “broadcast” distribution bypassed the need for any sort of buffering at all, and the I/O to the temporary tablespace disappeared completely. I’ll be writing more about that option some time in the future.

Posted by maceo

11 6, 2008 09:48 11 6, 2008 09:48
Response
No Trackback , a comment
RSS :
http://merritt.co.kr/tt/rss/response/117

latch:undo global data 경합


구글 찾아보면 undo segment 모자라면 발생할 수도 있다고 하는데 그건 못겪어봤다. 그때는 undo segment 늘려주면 되겠지.

내가 겪은건 latch:cache buffer chain 경합하고 본질적으로 똑같다.

DELETE를 대량으로 수행중인데, undo segment를 마구 읽어데는 세션이 계속 들어오면 undo segment에 대한 읽기때문에 latch 경합이 당연히 발생한다. 그게 latch: undo global data라고 표시가 된당... UPDATE도 마찬가지겠지.

결론은 버킹검... 이 아니라 -_-;

왜 OLTP 테이블에 DELETE를 대량으로 하냐고!!!!!

DML을 할 수 밖에 없는 이유를 일단 밝혀내서 되도록 하지 말게 하고, 정 해야겠으면 나눠서 처리한다던지.. 튜닝을 해야하겠다.


Posted by maceo

07 27, 2008 01:49 07 27, 2008 01:49
,
Response
No Trackback , No Comment
RSS :
http://merritt.co.kr/tt/rss/response/116

select로 인한 hotblock 경합 응급조치법~


oracle 운영은 첨이라 하나하나가 새롭다. 일단 흔히들 보는 hotblock 경합부터.

select문에 의한 latch: cache buffer chain 경합이면 쿼리 튜닝해야 하는데. (SQL Server에서는 PAGELATCH:SH경합 되겠다)

일단 튀는 쿼리 찾는거는 아래 스크립트

SELECT session_id, sql_id,cnt, NVL((select sql_text FROM v$sqlarea b WHERE a.sql_id=b.sql_id),'<EMPTY>')   sql_text
FROM (
      SELECT session_id, sql_id, COUNT(*) cnt
      FROM v$active_session_history
      WHERE session_state = 'ON CPU'
      AND sample_time > SYSDATE - (5 / (24 * 60 ))
      AND sql_id is not null
      GROUP BY session_id, sql_id
      ORDER BY COUNT(*) DESC) a
WHERE ROWNUM <= 50 ;


왠만하면 같은 쿼리가 주욱~ 나올건데, 일단 이넘을 잡자~

근데 이거하기전에 Front WAS는 거의 사망직전까지 간다. 진정시키려면 일단 경합이 일어난 블럭부터 찾자.

select sid,p1raw||', '||event||', '||p1text||', '||p1||', '||p2text||', '||p2||'
, '||p3text||', '||p3 as event
from v$session_Wait
where event not like '%SQL%' and event not like '%rdbms%'
and wait_class != 'Idle'
order by seconds_in_wait desc


p1raw가 경합이 일어난 블럭주소당... 이 블럭에 어떤 오브젝트들이 있는지 찾으려면~

select hladdr,
  obj,
  (
    select object_name
    from dba_objects
    where (data_object_id is null
          and object_id=x.obj)
      or data_object_id=x.obj
      and rownum = 1) as object_name,
  dbarfil,
  dbablk,
  tch
from x$bh x
where hladdr in ('C0000003177DC0D0')
order by hladdr, obj;


여기에 p1raw값 넣으면 이 블럭에 있는 오브젝트들이 나온다. 여기서 터치카운트가 높은 인덱스나 테이블이 있을건데, 아까 전에 뽑은 쿼리에서 건드리는 인덱스 / 테이블일 가능성이 높다.

만약 인덱스라면...

1. 응급조치로 alter index ... 해서 storage를 keep_pool 로 바꿔버리자. 재수좋으면 일시적으로 hotblock 경합이 사라진다.

2. 이게 안되면 시간이 좀 걸리겠지만 pctfree를 충분히 (50?) 주고 index online rebuild


저렇게 해놓고 시간 벌어놓은 다음에 급하게 쿼리 튜닝해서 WAS 패치하자~

Posted by maceo

07 27, 2008 01:42 07 27, 2008 01:42
,
Response
No Trackback , No Comment
RSS :
http://merritt.co.kr/tt/rss/response/115

oracle, sql server 단상

우와 이게 얼마만에 쓰는 글이냐... 그간 정말 다사다난한 1년 남짓이었다. ㅎㅎㅎ

각설하고,,,,

G마켓에서 SQL Server 2000 을 몇년, 2005 조금해보고 11번가에 와서 Oracle 몇달 운영해보면서 가진 느낌은,

"둘 다 똑같이 좋은 DB"

라는 것이다.

물론 오라클쪽이 상대적으로 기술적으로 앞선 부분이 여럿 보인다. 하지만 앞선 기술은 불안정성을 항상 유발하기 마련. 그게 언제나 좋은 것만은 아니다. 오히려 중요한 것은 DBMS의 기본인 데이터 및 시스템 안정성, 성능 측면에서 얼마나 고객을 안심시켜 줄 수 있는가? 하는 건데, SQL Server 그렇게 빡세게 돌리면서 데이터 깨져본 적 없고, 성능 문제는 사실 튜닝하기 나름이었다는 생각이다. 2005에 와서는 G마켓 DB가 30,000 Batch Request / sec 을 보이고 있다는 무시무시한 결과도 있고 하니... SQL Server 가 성능 떨어진다는 얘기는 완전히 옛말이다.

운영 측면에서 봐도 SQL Server가 처음에 접근하기는 더 편하다는 느낌을 가진다. 오라클은 굉장히 자유도가 높은 미국식 RPG라면 SQL Server는 일본식 RPG에 가깝다고 해야하나... 물론 DB 엔진의 깊숙한 부분까지 파라미터 바꿔가면서 제어하기를 원한다면 오라클이 좋겠으나 대부분 그렇게까지 할 필요가 없는 경우가 많고, 그럴때는 깊숙한 제어가 힘든 SQL Server의 "상대적" 단순함이 오히려 장점이 될 수도 있겠다.

결국 SQL Server 던 오라클이던 쓰는 사람의 숙련도에 따른다는 심심하고 재미없는 결론이 되겠다. Java가 좋으냐 .NET이 좋으냐 하고 싸우는거나 똑같다고나 할까 -_-;;;;;;



@요즘같은 웹서비스 세상에서는 굳이 오라클, SQL Server 안써도 된다. MySQL로도 플리커나 심지어는 Facebook같은 글로벌 웹서비스 충분히 돌릴 수 있다. 어느정도 레퍼런스 많이 쌓인 DB는 모두가 다 좋은 DB고 다들 쓰기나름이다 (Sybase던, Informix던 DB2던 마찬가지겠지? ㅎㅎㅎ)

Posted by maceo

07 27, 2008 01:08 07 27, 2008 01:08
Response
No Trackback , No Comment
RSS :
http://merritt.co.kr/tt/rss/response/114

병목 제거의 양면성?

오늘 우리팀에 보낸 메일...
95% 이상의 확률로 병목제거는 좋은 일이다.
하지만 부하가 과도한 상황이라면 병목제거도 양면성을 가진다.

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

Tempdb
를 메모리 디스크로 바꾸고 난 후 재미있는 현상이 생겼습니다.

 

오후 3 경 동일 시간대 기준으로 Batch Request/sec 25% 증가했지만 CPU 사용량도 평균 10% 정도 증가했습니다. Page Split/sec 도 전반적으로 늘었습니다. 즉 템프쪽의 병목이 빠지면서 이것저것 더 빨리 처리하다보니 CPU와 디스크가 힘들어하는 상황이 된 것이죠. 반면 분당 거래건도 조금 늘었습니다. 고객들이 보기에 페이지가 좀 더 빨리 뜬다는 효과도 있겠지요. 하지만 전체적으로 보면 병목 제거가 모든 면에서 좋은 건 아니고 오히려 가려져있던 부하를 좀 더  드러내는 효과도 있는 것 같습니다. 적당한 병목은 일종의 버퍼 역할을 해준다고 볼 수도 있는 걸까요? -_-;;;;

 

저희 회사의 중요한 병목구간들인 과도한 인덱스, 데이터, 템프 DB, 프론트의 COM을 이용한 DB호출 (테스트결과 COM을 이용하는 것 보다 ASP에서 커맨드 객체를 바로 이용해서 콜하는게 웹서버 부하를 20% 이상 경감시킵니다. 즉 웹서버가 더 빨리 돌고, 단위시간당 DB에 콜이 더 많이 들어오게 되는거죠) 그런 요소들을 제거하면 할수록 오히려 더욱 시스템이 더 많은 부하를 견뎌야 할 지도 모르겠습니다. 이론적으로 인덱스 정리, 데이터 분리가 IO부하를 감소시키기는 하지만 전체적으로 처리량을 늘림으로써

페이지 스플릿을 더욱 확대시킬지도 모르겠네요. 오히려 서버 분산, 캐쉬 DB 등 아키텍쳐적인 개선이  더욱 시급한게 아닌가.. 하는 생각도 듭니다. 누가 시스템이 살아있는 유기체같다는 말을 하던데 그 말이 실감이 되는 요즘입니다.


Posted by maceo

06 21, 2007 15:44 06 21, 2007 15:44
, , ,
Response
No Trackback , a comment
RSS :
http://merritt.co.kr/tt/rss/response/103

LINQ and Functional Programming

SQL Server 2008 관련된 동영상을 보다가 Entity Data Platform 이란거에 대해서 좀 봤고 LINQ라는 것에 대해서 좀 봤고, 최근에 알게된 Erlang 에도 생각이 미치더니만, 괜한 공상이 머릿속을 흔든다.

앞으로도 DB의 중요성은 절대 줄어들지 않을 것 같으므로 정확하고 효율적인 모델링, 능숙한 SQL사용, 성능과 저장공간을 동시에 고려한 최적의 인덱스 작성 능력은 언제나 요구되는 능력일 것이다. DB를 기반으로 한 솔루션에서 대부분의 병목은 DB에서 발생하기 때문이다. 그런데 인터넷 세상이 되고 비즈니스에서 속도가 강조되면서 개발도 함께 빨라져야할 필요가 생기고 있다. 더불어 유지보수의 용이함도 꼭 필요하다. 이런 측면에서 객체 모델의 도입이 필요하다. 하지만  OR impedence 를 해결하는게 쉬운 일은 아니며, 이를 위한 OR매핑 솔루션의 사용도 결코 쉽지 않다. 만들기는 더더욱 어렵다.

SQL2008 white paper 를 읽다보니 개발측면에서 흥미로운게 눈에 띄었는데, ADO, Visual Studio 와 함께 결합한 LINQ라는 놈이다. Language Integrated Query 라는건데, DB에 SQL로 질의를 하는게 아니라(테이블 데이터를 내놔라! 하는 기존방식) 고객 엔티티를 내놔라! 하고 요청하고 그 결과가 DataSet이 아니라 .NET 객체로 반환되는 것이다. 아... 이건 사실상의 OR매퍼가 아닌가? 다음과 같은 syntax로 사용한다.

public partial class DataDemos_1_HelloWorld : System.Web.UI.
{
    protected void Page_Load(object sender, EventArgs e)
    {
        AdventureWorks db = new AdventureWorks();

        var query = from person in db.SalesPeople
                         where person.HireData > new DataTime(2002,1,1)
                          select person;

        DataList1.DataSource = query;
        DataList1.DataBind();
    }
}

코드를 보자. 저거 ASP.NET 코드다. 

근데 AdventureWorks 라니? 그렇다.. SQL2005 의 데모 DB인 AdventureWorks를 객체로 이해하고 접근하고 있다. db.SalesPeople 는 저 DB에 있는 스키마일까? person 은 어떤 테이블하고 매핑되어 있는 것일까? 글고 원본 그림을 보면 "Results are .NET objects, strongly typed, support data binding" 이라고 되어 있다. 음.. 별도의 비즈니스 객체 레이어를 생성해줘야지 거기다가 메서드를 추가할텐데...  혹시 매핑관계를 별도로 다 정의해줘야 하는걸까? 그 노가다도 상당히 골때릴텐데... 게다가 저 놈이 결국 DB에 날리는건 SQL일텐데 이 놈의 성능은 어떨까? 객체를 Persist 할 때 만들어 내는 SQL은 객체의 프라퍼티중 변경된 넘들만 들어가게 만들어 줄까? DBA입장에서 보기엔 DB관리가 점점 더 힘들어지는 상황인 것 같다.

관련 링크 : http://link.allblog.net/4060901/http://pobeez.egloos.com/218530
관련 링크 : http://blogs.msdn.com/charlie/archive/2007/01/26/anders-hejlsberg-on-linq-and-functional-programming.aspx (동영상 꼭 볼것!)

동영상을 보면 대충 뭘 하려는지 짐작이 된다. 데이터를 다루는 시스템을 예로 들어보자. 결국 다른 소스에서 온 서로 다른 데이터들을 이리저리 엮고, 계산해서 보여주고 인풋을 계산해서 보여주는거 아니던가 말이다. LINQ가 하려는건 언어에 데이터를 다루는 기능을 통합하되, 그걸 좀 더 추상적인 레벨에서 수행할 수 있게 하는거다. 음.. 간단하게 설명하자면 C#같은 언어에 DB의 쿼리 엔진같은넘이 들어간다고 보면 되겠다. 동영상에 함수형 프로그래밍이니 지연된 실행이니 타입 추론이니 어쩌구 나오는데... 설명 가만 들어보면 SQL을 DB가 받아서 parse tree만들고 그러면서 타입체크하고 실행 계획 만들면서 CPU갯수 체크해서 병렬로 돌릴 수 있는거 돌리고... 그러는거랑 LINQ에서 추구하는게 대단히 흡사하다. 얘네들은 언어레벨에서 이걸 하려고 하는거고, 이렇게 하기 위한 최고의 방법이 함수형 언어 패러다임의 도입이고, 이것의 밑바탕은 선형 대수이고 이게 되면 람다식을 해석해서 만든 expression tree를 보고 CPU남으면 병렬로 돌릴지 결정하는거도 쉽게 쉽게 할 수 있다는 말이 되겠다.

LINQ를 자유자재로 쓰게 되면 XML하고 텍스트, DB데이터를 필터링해서 읽어온다음 join 해서 group by 한 다음에 결과를 어디로 보내는 작업을 DB에서 쿼리 만들듯이 할 수 있게 될거다. 중간에 Hashtable에 넣고 Vector에 넣고 이러는거 하나도 없다. 알아서 한다는게 동영상의 내용으로 보인다. 뭐, DB에서 현재 하고 있는거랑 똑같자나? ETL툴들도 여러 데이터 소스에서 읽어와서 join , group by 하는거 다 된다. 특정한 모듈, 이를테면 DW에서 많이 쓰는 lookup 같은것도 CPU 여러개 쓰면서 잘 돈다. 데이터를 핸들링하는 과정이 단위 기능으로 쪼개졌기 때문이다.

자 그러면 이런 문제를 자연스럽게 예상할 수 있다. expression tree를 만드는게 얼마나 효율적인 것이냐. DB에서 실행 계획 만드는게 의외로 CPU많이 쓰는 작업임을 생각해보면 이런 문제도 분명히 생길거다. 글고 실행계획 재사용같은게 되려나? 앗. 프로그램이니까 컴파일이 되는구나-_-; 실행계획 재사용에 해당하는 문제는 없겠다. ㅋㅋㅋ 근데 만들어낸 expression tree가 진짜로 최적일거냐 하는 문제는 여전히 예상이 된다.  두개의 XML소스와 세개의 텍트스 소스와 하나의 DB에서 데이터를 읽어서 join을 한다고 치면 뭘 먼저 읽고 어떤 순서로 join 할지는 상황에 따라 최적값이 다를텐데. 뭐, 이건 DB 튜닝을 하다보면 맨날 만나는 문제지만. 그럴때는 지가 알아서 컴파일된 코드를 바꿀까???? ㅋㅋㅋ

적으면서 생각을 정리하다보니 비전은 원대하나 실무에서 검증되기엔 많은 난관이 있어보인다는 생각이 든다. 게다가... 객체도 잘 모르는 대다수의 개발자들이 함수형 프로그래밍도 공부해야 한다는 어려움도 예상이 되는구먼. 절차지향, 집합지향, 객체지향, AOP, 함수형 등등 뭐가 이리 많단 말이냐. ㅎㅎㅎㅎ


@제가 FP나 LINQ에는 전혀 경험이 없으니 위의 동영상을 깊이있게 이해하지 못하거나 오해하고 있는 부분이 있을 수 있습니다. 추가하실 말씀이 있으시면 덧글로 가르침을 주시면 감사하겠습니다.

Posted by maceo

06 7, 2007 17:01 06 7, 2007 17:01
, , , , ,
Response
No Trackback , a comment
RSS :
http://merritt.co.kr/tt/rss/response/102

« Previous : 1 : 2 : 3 : 4 : 5 : 6 : ... 8 : Next »

블로그 이미지

가늘어도 긴놈이 장땡

- maceo

Archives

Authors

  1. maceo

Calendar

«   9 2010   »
      1 2 3 4
5 6 7 8 9 10 11
12 13 14 15 16 17 18
19 20 21 22 23 24 25
26 27 28 29 30    

Site Stats

Total hits:
179883
Today:
2
Yesterday:
35