PostgreSQL 테이블 행의 크기 측정


83

PostgreSQL 테이블이 있습니다. select *아주 느리지 만 select id멋지고 빠릅니다. 나는 행의 크기가 매우 커서 운송하는 데 시간이 걸리거나 다른 요인 일 수 있다고 생각합니다.

모든 필드 (또는 거의 모든 필드)가 필요하므로 하위 집합 만 선택하는 것이 빠른 해결책은 아닙니다. 원하는 필드를 선택하는 것이 여전히 느립니다.

다음은 테이블 스키마에서 이름을 뺀 것입니다.

integer                  | not null default nextval('core_page_id_seq'::regclass)
character varying(255)   | not null
character varying(64)    | not null
text                     | default '{}'::text
character varying(255)   | 
integer                  | not null default 0
text                     | default '{}'::text
text                     | 
timestamp with time zone | 
integer                  | 
timestamp with time zone | 
integer                  | 

텍스트 필드의 크기는 임의의 크기 일 수 있습니다. 그러나 최악의 경우 몇 킬로바이트를 넘지 않아야합니다.

질문

  1. '미친 비효율적'이라고 비명을 지르는 것이 있습니까?
  2. Postgres 명령 줄에서 페이지 크기를 측정하여이를 디버깅하는 데 도움이되는 방법이 있습니까?

실제로 ... 열 중 하나는 11MB입니다. 그것은 내가 생각하는 것을 설명 할 것입니다. 그래서 length(*)그냥 하는 것보다 할 수있는 방법이 length(field)있습니까? 바이트가 아닌 문자이지만 대략적인 값만 필요하다는 것을 알고 있습니다.
Joe

답변:


101

Q2 : way to measure page size

PostgreSQL은 많은 데이터베이스 객체 크기 함수를 제공 합니다. 이 쿼리에서 가장 흥미로운 것을 포장 하고 하단에 통계 액세스 기능 을 추가했습니다 . (추가 모듈 pgstattuple 은 더 유용한 기능을 제공합니다.)

이것은 "행의 크기"를 측정하는 다른 방법이 매우 다른 결과를 초래한다는 것을 보여줄 것입니다. 그것은 모두 정확하게 측정하고자하는 것에 달려 있습니다.

이 쿼리에는 Postgres 9.3 이상 이 필요합니다 . 이전 버전의 경우 아래를 참조하십시오.

하위 쿼리 VALUES에서 표현식을LATERAL 사용하여 모든 행에 대한 계산 철자를 피하십시오.

교체 public.tbl하여 행의 크기에 대한 수집 된 통계의 컴팩트보기를 얻을 수 있도록 검증을 거친 스키마 테이블 이름 (2 회). 반복 사용을 위해 이것을 plpgsql 함수로 감싸서 테이블 이름을 매개 변수로 사용하고 EXECUTE...

SELECT l.metric, l.nr AS "bytes/ct"
     , CASE WHEN is_size THEN pg_size_pretty(nr) END AS bytes_pretty
     , CASE WHEN is_size THEN nr / NULLIF(x.ct, 0) END AS bytes_per_row
FROM  (
   SELECT min(tableoid)        AS tbl      -- = 'public.tbl'::regclass::oid
        , count(*)             AS ct
        , sum(length(t::text)) AS txt_len  -- length in characters
   FROM   public.tbl t                     -- provide table name *once*
   ) x
 , LATERAL (
   VALUES
      (true , 'core_relation_size'               , pg_relation_size(tbl))
    , (true , 'visibility_map'                   , pg_relation_size(tbl, 'vm'))
    , (true , 'free_space_map'                   , pg_relation_size(tbl, 'fsm'))
    , (true , 'table_size_incl_toast'            , pg_table_size(tbl))
    , (true , 'indexes_size'                     , pg_indexes_size(tbl))
    , (true , 'total_size_incl_toast_and_indexes', pg_total_relation_size(tbl))
    , (true , 'live_rows_in_text_representation' , txt_len)
    , (false, '------------------------------'   , NULL)
    , (false, 'row_count'                        , ct)
    , (false, 'live_tuples'                      , pg_stat_get_live_tuples(tbl))
    , (false, 'dead_tuples'                      , pg_stat_get_dead_tuples(tbl))
   ) l(is_size, metric, nr);

결과:

              메트릭 | 바이트 / ct | bytes_pretty | bytes_per_row
----------------------------------- + ---------- + --- ----------- + ---------------
 core_relation_size | 44138496 | 42MB | 91
 visible_map | 0 | 0 바이트 | 0
 free_space_map | 32768 | 32 킬로바이트 | 0
 table_size_incl_toast | 44179456 | 42MB | 91
 indexes_size | 33128448 | 32MB | 68
 total_size_incl_toast_and_indexes | 77307904 | 74MB | 159
 live_rows_in_text_representation | 29987360 | 29MB | 62
 ------------------------------ | | |
 row_count | 483424 | |
 live_tuples | 483424 | |
 dead_tuples | 2677 | |

이전 버전 (Postgres 9.2 이상) :

WITH x AS (
   SELECT count(*)               AS ct
        , sum(length(t::text))   AS txt_len  -- length in characters
        , 'public.tbl'::regclass AS tbl      -- provide table name as string
   FROM   public.tbl t                       -- provide table name as name
   ), y AS (
   SELECT ARRAY [pg_relation_size(tbl)
               , pg_relation_size(tbl, 'vm')
               , pg_relation_size(tbl, 'fsm')
               , pg_table_size(tbl)
               , pg_indexes_size(tbl)
               , pg_total_relation_size(tbl)
               , txt_len
             ] AS val
        , ARRAY ['core_relation_size'
               , 'visibility_map'
               , 'free_space_map'
               , 'table_size_incl_toast'
               , 'indexes_size'
               , 'total_size_incl_toast_and_indexes'
               , 'live_rows_in_text_representation'
             ] AS name
   FROM   x
   )
SELECT unnest(name)                AS metric
     , unnest(val)                 AS "bytes/ct"
     , pg_size_pretty(unnest(val)) AS bytes_pretty
     , unnest(val) / NULLIF(ct, 0) AS bytes_per_row
FROM   x, y

UNION ALL SELECT '------------------------------', NULL, NULL, NULL
UNION ALL SELECT 'row_count', ct, NULL, NULL FROM x
UNION ALL SELECT 'live_tuples', pg_stat_get_live_tuples(tbl), NULL, NULL FROM x
UNION ALL SELECT 'dead_tuples', pg_stat_get_dead_tuples(tbl), NULL, NULL FROM x;

같은 결과입니다.

Q1 : anything inefficient?

행 정렬 을 최적화 하여 행당 일부 바이트를 저장하고 현재 정렬 패딩에 낭비되었습니다.

integer                  | not null default nextval('core_page_id_seq'::regclass)
integer                  | not null default 0
character varying(255)   | not null
character varying(64)    | not null
text                     | default '{}'::text
character varying(255)   | 
text                     | default '{}'::text
text                     |
timestamp with time zone |
timestamp with time zone |
integer                  |
integer                  |

이는 행당 8-18 바이트를 절약합니다. 나는 그것을 "열 테트리스" 라고 부릅니다 . 세부:

또한 다음을 고려하십시오.


테이블이 비어 있으면 9.3 이전의 스 니펫은 0으로 나누기를 던집니다. 실제로 9.3+ 버전을 사용하고 싶었지만 실수로 잘못된 버전을 고르고 수정하는 데 몇 시간을 소비해야했습니다. 이제는 시간을 낭비 할 수 없습니다. 교체 , unnest(val) / ct, (LEAST(unnest(val), unnest(val) * ct)) / (ct - 1 + sign(ct))그리고 포기하지 않습니다. 이유는 경우이다 ct0, val에 의해 대체 될 0ct대체한다 1.
GuiRitter

1
@GuiRitter : 지적 해 주셔서 감사합니다. 그래도 더 간단한 수정을 적용했습니다. 또한 일반적인 업데이트가 있지만 쿼리는 동일하게 유지됩니다.
Erwin Brandstetter

35

TOAST 내용을 포함하여 행 크기의 근사값은 전체 행의 TEXT 표현 길이를 쿼리하여 쉽게 얻을 수 있습니다.

SELECT octet_length(t.*::text) FROM tablename AS t WHERE primary_key=:value;

이것은 실행할 때 클라이언트 측에서 검색되는 바이트 수에 가까운 근사치입니다.

SELECT * FROM tablename WHERE primary_key=:value;

... 쿼리 호출자가 텍스트 형식으로 결과를 요청한다고 가정하면, 대부분의 프로그램에서 수행하는 이진 형식이 가능하지만 대부분의 경우 문제가되지는 않습니다.

다음과 같은 N"텍스트에서 가장 큰"행 을 찾기 위해 동일한 기술을 적용 할 수 있습니다 tablename.

SELECT primary_key, octet_length(t.*::text) FROM tablename AS t
   ORDER BY 2 DESC LIMIT :N;

빅 데이터로 작업 할 때 몇 가지 추정치를 빠르게 얻을 수있는 훌륭한 방법입니다 (예 : 대부분의 행 크기는 가변 길이 토스트 저장 열에 있음).
fgblomqvist

14

일어날 수있는 몇 가지가 있습니다. 일반적으로 길이가 근위 문제라고 의심합니다. 대신 길이 관련 문제가 있다고 생각합니다.

당신은 텍스트 필드가 최대 몇 k를 얻을 수 있다고 말합니다. 주 기억 장치에서 행이 8k를 넘을 수 없으며, 더 큰 텍스트 필드가 TOASTed 이거나 주 기억 장치에서 별도의 파일로 확장 기억 장치로 이동했을 수 있습니다. 이렇게하면 주 기 o 장치가 더 빨라집니다 (따라서 액세스 할 디스크 페이지 수가 적어 select id가 더 빠릅니다). 그러나 무작위 I / O가 많으 G로 select *가 느려집니다.

전체 행 크기가 여전히 8k 미만이면 저장소 설정을 변경해 볼 수 있습니다. 그러나 대형 스토리지 속성을 주 스토리지에 삽입하면 나쁜 일이 생길 수 있으므로 필요하지 않은 경우이를 건드리지 않는 것이 가장 좋습니다. 그렇지 않으면 확인 제한을 통해 적절한 제한을 설정하십시오. 따라서 운송 만이 유일한 것은 아닙니다. 임의의 읽기가 필요한 많은 필드를 모을 수 있습니다. 많은 수의 임의 읽기는 캐시 누락을 유발할 수 있으며 필요한 많은 양의 메모리는 결합이 존재하는 경우 (TOAST가 포함 된 경우) 디스크에서 구체화되고 많은 수의 넓은 행을 요구할 수 있으므로 비용이 더 많이들 수 있습니다. 조인 패턴 등

내가 볼 첫 번째 일은 적은 행을 선택하고 그것이 도움이되는지 확인하는 것입니다. 그래도 서버에 RAM을 추가 할 수는 있지만 계획 변경 및 캐시 누락으로 인해 성능이 저하되는 위치부터 시작합니다.


당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.