현재 PostgreSQL (8.0 포함) 버전들의 쿼리 최적화기(optimizer)에는 TOAST 테이블과 관련된 약점이 있습니다. TOAST 테이블은 일반 데이터 페이지에 적합하지 않은 (긴 텍스트, 큰 이미지 또는 수많은 꼭짓점을 가진 복잡 도형 같은) 대용량(데이터 크기 관점에서) 값을 저장하는 데 쓰이는 일종의 "확장 공간(extension room)"입니다. 자세한 정보는 the PostgreSQL Documentation for TOAST 를 참고하십시오.
상당히 대용량의 도형을 가진 테이블이지만, (전체 유럽 국가들의 고해상도 국경을 담고 있는 테이블처럼) 도형 행이 그렇게 많지 않을 경우 문제가 드러납니다. 이때 테이블 자체는 저용량이지만, 많은 TOAST 공간을 차지합니다. 이번 예제의 경우, 테이블 자체는 약 80행을 담고 있고 데이터 페이지 3장만 사용하지만, TOAST 테이블은 8,225페이지를 차지합니다.
이 테이블의 행 가운데 몇 행과만 일치하는 경계 상자를 검색하기 위한 도형 연산자 &&를 이용하는 쿼리를 전송해봅시다. 이때 쿼리 최적화기는 해당 테이블이 데이터 페이지 3장과 80행만 가지고 있다고 판단합니다. 이런 저용량 테이블에는 인덱스보다 순차 스캔이 훨씬 빠를 거라고 추정할 것입니다. 따라서 GiST 인덱스를 무시하기로 합니다. 일반적인 경우라면 이 추정이 맞습니다. 그러나 이번 예제의 경우, && 연산자가 경계 상자를 비교하기 위해 디스크에서 모든 도형을 불러와야 하기 때문에, 결국 모든 TOAST 페이지도 읽어야 합니다.
사용자 시스템에 이 버그가 있는지 확인하려면, "EXPLAIN ANALYZE" PostgreSQL 명령어를 실행하십시오. 자세한 정보 및 기술적인 세부 사항을 알고 싶다면, PostgreSQL 성능 메일링 리스트에 있는 다음 스레드를 읽어볼 수 있습니다: http://archives.postgresql.org/pgsql-performance/2005-02/msg00030.php
and newer thread on PostGIS https://lists.osgeo.org/pipermail/postgis-devel/2017-June/026209.html
PostgreSQL 개발자들은 쿼리 추정이 TOAST를 인식하도록 만들어 이 문제를 해결하려 하고 있습니다. 현 시점에서는, 다음 두 가지 해결 방법이 있습니다:
첫 번째 해결책은 쿼리 설계자가 인덱스를 사용하도록 강제하는 방법입니다. 쿼리를 전송하기 전에 서버로 "SET enable_seqscan TO off;"를 전송하십시오. 이렇게 하면 기본적으로 쿼리 설계자가 순차 스캔을 사용할 수 있는 경우에도 사용하지 않도록 강제합니다. 따라서 평상시처럼 GiST 인덱스를 이용할 것입니다. 그러나 이 플래그를 매번 연결할 때마다 설정해야 하고 다른 상황에서 쿼리 설계자가 잘못 추정하도록 하기 때문에, 쿼리가 끝난 후에 "SET enable_seqscan TO on;"을 전송해야 합니다.
두 번째 해결책은 쿼리 설계자가 추정하는 만큼 순차 스캔 속도를 높이는 방법입니다. 경계 상자를 "캐시"하는 추가 열을 생성한 다음, 이 추가 열을 대상으로 일치 여부를 쿼리하면 됩니다. 이번 예제의 경우, 해당 명령어는 다음과 같습니다:
SELECT AddGeometryColumn('myschema','mytable','bbox','4326','GEOMETRY','2'); UPDATE mytable SET bbox = ST_Envelope(ST_Force2D(geom));
이제 geom_column 대신 bbox 에 대해 && 연산자를 이용하도록 다음과 같이 사용자 쿼리를 변경하십시오:
SELECT geom_column FROM mytable WHERE bbox && ST_SetSRID('BOX3D(0 0,1 1)'::box3d,4326);
물론, mytable의 열을 변경하거나 추가할 경우, bbox의 "일관성"을 유지해야 합니다. 일관성을 유지시키는 가장 명료한 방법은 트리거(trigger)일 것입니다. 물론 사용자 응용 프로그램을 수정해서 bbox 열의 정보를 항상 최신 상태로 유지하도록 하거나, 모든 수정 작업 후 매번 앞에서 나온 UPDATE 쿼리를 실행할 수도 있습니다.
레코드 대부분이 읽기 전용이고, 쿼리의 대다수가 단일 인덱스를 이용하는 테이블의 경우, PostgreSQL은 CLUSTER 명령어를 제공합니다. 이 명령어는 모든 데이터 행을 인덱스 기준과 동일한 순서로 물리적으로 재정렬합니다. 이렇게 하면 성능 향상 관련 두 가지 장점이 나타납니다. 첫째, 인덱스 범위 스캔을 하는 경우 데이터 테이블에 대한 요청 횟수가 극단적으로 줄어듭니다. 둘째, 사용자가 작업중인 집합이 인덱스 상의 조금 작은 간격에 집중하고 있을 경우 데이터 행이 더 적은 데이터 페이지를 따라 퍼져 있기 때문에 더 효율적인 캐시 작업이 가능합니다. (이 시점에서 PostgreSQL 지침서에 있는 CLUSTER 명령어 문서를 읽어보는 편이 좋습니다.)
하지만, 현재 PostgreSQL은 PostGIS GiST 인덱스에 대한 클러스터 작업을 허용하지 않습니다. 왜냐하면 GiST 인덱스가 NULL 값을 단순히 무시하기 때문에, 다음과 같은 오류 메시지를 보게 될 것입니다:
lwgeom=# CLUSTER my_geom_index ON my_table; ERROR: cannot cluster when index access method does not handle null values HINT: You may be able to work around this by marking column "geom" NOT NULL.
HINT 메시지의 내용대로, 테이블에 "not null" 제약조건을 추가하면 이 결점을 피해 갈 수 있습니다:
lwgeom=# ALTER TABLE my_table ALTER COLUMN geom SET not null; ALTER TABLE
물론, 사용자 도형 열 안에 실제로 NULL 값이 필요한 경우 이 방법을 쓸 수는 없습니다. 또한 제약조건을 추가하려면 앞의 메소드를 이용해야 합니다. "ALTER TABLE blubb ADD CHECK (geometry is not null);"처럼 CHECK 제약조건을 사용하는 방법은 먹히지 않을 겁니다.
때때로 사용자 테이블에 3D 또는 4D 데이터가 있지만, 항상 2D 도형만을 출력하는 OpenGIS 준수 ST_AsText() 또는 ST_AsBinary() 함수를 통해서만 접근하는 경우가 있을 수 있습니다. 이런 경우 내부적으로 ST_Force2D() 함수를 호출하는데, 대용량 도형의 경우 이 함수는 시스템의 자원을 상당히 잡아먹습니다. 이런 자원 낭비를 피하려면, 미리 그 추가적인 차원을 완전히 삭제하는 편이 효율적일 수 있습니다.
UPDATE mytable SET geom = ST_Force2D(geom); VACUUM FULL ANALYZE mytable;
AddGeometryColumn() 함수를 통해 사용자 도형 열을 추가했을 경우 도형 차원에 제약조건이 걸려 있다는 사실을 주의하십시오. 이를 우회하려면 제약조건을 삭제해야 합니다. geometry_columns 테이블 내부 항목을 업데이트한 다음 제약조건을 다시 생성하는 걸 잊지 마십시오.
대용량 테이블의 경우, 사용자의 기본 키 또는 또다른 사용 가능한 기준과 함께 WHERE 절을 이용해서 테이블의 일부분만 업데이트하도록 제한한 다음 업데이트마다 간단히 "VACUUM;"을 실행하는 방식으로 이 업데이트를 작은 부분으로 나누는 편이 좋을 수 있습니다. 이렇게 하면 필요한 임시 디스크 공간이 급격히 줄어듭니다. 또한 몇 종류의 차원이 섞인 도형들을 가지고 있을 경우, "WHERE dimension(the_geom)>2"로 업데이트를 제한하면 이미 2D인 도형을 다시 작성하는 일을 건너뛸 수 있습니다.