날짜를 이용한 인덱스 최적화


26

PostgreSQL 9.0.8에는 큰 객체 테이블 (15M + 행)이 있는데 구식 필드를 쿼리하려고합니다.

확장 성 및 동시성 목적으로 쿼리를 수백만으로 나누고 며칠 전 날짜가 updated_at 필드 인 모든 데이터를 가져오고 싶습니다.

백만 개의 ID로 많은 색인과 쿼리를 시도했지만 Heroku의 Ronin 하드웨어로 100 초 미만의 성능을 얻을 수없는 것 같습니다.

가능한 한 효율적으로 만들려고하지 않은 제안을 찾고 있습니다.

시도 # 1

 EXPLAIN ANALYZE SELECT count(*) FROM objects
 WHERE (date(updated_at)) < (date(now())-7) AND id >= 5000001 AND id < 6000001;
 INDEX USED: (date(updated_at),id)
 268578.934 ms

시도 # 2

 EXPLAIN ANALYZE SELECT count(*) FROM objects
 WHERE ((date(now()) - (date(updated_at)) > 7)) AND id >= 5000001 AND id < 6000001;
 INDEX USED: primary key
 335555.144 ms

시도 # 3

 EXPLAIN ANALYZE SELECT count(*) FROM objects
 WHERE (date(updated_at)) < (date(now())-7) AND id/1000000 = 5;
 INDEX USED: (date(updated_at),(id/1000000))
 243427.042 ms

시도 # 4

 EXPLAIN ANALYZE SELECT count(*) FROM objects
 WHERE (date(updated_at)) < (date(now())-7) AND id/1000000 = 5 AND updated_at IS NOT NULL;
 INDEX USED: (date(updated_at),(id/1000000)) WHERE updated_at IS NOT NULL 
 706714.812 ms

TRY # 5 (1 개월의 오래된 데이터)

 EXPLAIN ANALYZE SELECT count(*) FROM objects
 WHERE (EXTRACT(MONTH from date(updated_at)) = 8) AND id/1000000 = 5;
 INDEX USED: (EXTRACT(MONTH from date(updated_at)),(id/1000000))
 107241.472 ms

시도 # 6

 EXPLAIN ANALYZE SELECT count(*) FROM objects
 WHERE (date(updated_at)) < (date(now())-7) AND id/1000000 = 5;
 INDEX USED: ( (id/1000000 ) ASC ,updated_at DESC NULLS LAST)
 106842.395 ms

7 번 시도 ( http://explain.depesz.com/s/DQP 참조 )

 EXPLAIN ANALYZE SELECT count(*) FROM objects
 WHERE id/1000000 = 5 and (date(updated_at)) < (date(now())-7);
 INDEX USED: ( (id/1000000 ) ASC ,date(updated_at) DESC NULLS LAST);
 100732.049 ms
 Second try: 87280.728 ms 

시도 # 8

 EXPLAIN ANALYZE SELECT count(*) FROM objects
 WHERE (date(updated_at)) < (date(now())-7) AND id/1000000 = 5 AND updated_at IS NOT NULL;
 INDEX USED:  ( (id/1000000 ) ASC ,date(updated_at) ASC NULLS LAST);
 129133.022 ms

TRY # 9 ( Erwin의 제안에 따른 부분 색인 , http://explain.depesz.com/s/p9A 참조 )

 EXPLAIN ANALYZE SELECT count(*) FROM objects
 WHERE id BETWEEN 5000000 AND 5999999 AND (date(updated_at)) < '2012-10-23'::date;
 INDEX USED: (date(updated_at) DESC NULLS LAST)
 WHERE id BETWEEN 5000000 AND 6000000 AND date(updated_at) < '2012-10-23'::date;
 73861.047 ms

# 10을 시도하십시오 ( Erwin의 제안에 따라 CLUSTER ).

 CREATE INDEX ix_8 on objects ( (id/1000000 ) ASC ,date(updated_at) DESC NULLS LAST);
 CLUSTER entities USING ix_8;
 EXPLAIN ANALYZE SELECT count(*) FROM objects
 WHERE id/1000000 = 5 and (date(updated_at)) < (date(now())-7) ;
 4745.595 ms

 EXPLAIN ANALYZE SELECT count(*) FROM objects
 WHERE id/1000000 = 10 and (date(updated_at)) < (date(now())-7) ;
 17573.639 ms

==>이 솔루션이 승리 한 것 같습니다. 애플리케이션의 어느 곳에서나 반작용을 확인하기 위해 철저히 테스트해야합니다.

DB 설정 :

pg_settings에서 name, min_val, max_val, boot_val을 선택하십시오.

             name               |  min_val  |   max_val    |     boot_val      
--------------------------------+-----------+--------------+-------------------
allow_system_table_mods         |           |              | off
application_name                |           |              | 
archive_command                 |           |              | 
archive_mode                    |           |              | off
archive_timeout                 | 0         | 2147483647   | 0
array_nulls                     |           |              | on
authentication_timeout          | 1         | 600          | 60
autovacuum                      |           |              | on
autovacuum_analyze_scale_factor | 0         | 100          | 0.1
autovacuum_analyze_threshold    | 0         | 2147483647   | 50
autovacuum_freeze_max_age       | 100000000 | 2000000000   | 200000000
autovacuum_max_workers          | 1         | 536870911    | 3
autovacuum_naptime              | 1         | 2147483      | 60
autovacuum_vacuum_cost_delay    | -1        | 100          | 20
autovacuum_vacuum_cost_limit    | -1        | 10000        | -1
autovacuum_vacuum_scale_factor  | 0         | 100          | 0.2
autovacuum_vacuum_threshold     | 0         | 2147483647   | 50
backslash_quote                 |           |              | safe_encoding
bgwriter_delay                  | 10        | 10000        | 200
bgwriter_lru_maxpages           | 0         | 1000         | 100
bgwriter_lru_multiplier         | 0         | 10           | 2
block_size                      | 8192      | 8192         | 8192
bonjour                         |           |              | off
bonjour_name                    |           |              | 
bytea_output                    |           |              | hex
check_function_bodies           |           |              | on
checkpoint_completion_target    | 0         | 1            | 0.5
checkpoint_segments             | 1         | 2147483647   | 3
checkpoint_timeout              | 30        | 3600         | 300
checkpoint_warning              | 0         | 2147483647   | 30
client_encoding                 |           |              | SQL_ASCII
client_min_messages             |           |              | notice
commit_delay                    | 0         | 100000       | 0
commit_siblings                 | 1         | 1000         | 5
constraint_exclusion            |           |              | partition
cpu_index_tuple_cost            | 0         | 1.79769e+308 | 0.005
cpu_operator_cost               | 0         | 1.79769e+308 | 0.0025
cpu_tuple_cost                  | 0         | 1.79769e+308 | 0.01
cursor_tuple_fraction           | 0         | 1            | 0.1
custom_variable_classes         |           |              | 
DateStyle                       |           |              | ISO, MDY
db_user_namespace               |           |              | off
deadlock_timeout                | 1         | 2147483      | 1000
debug_assertions                |           |              | off
debug_pretty_print              |           |              | on
debug_print_parse               |           |              | off
debug_print_plan                |           |              | off
debug_print_rewritten           |           |              | off
default_statistics_target       | 1         | 10000        | 100
default_tablespace              |           |              | 
default_text_search_config      |           |              | pg_catalog.simple
default_transaction_isolation   |           |              | read committed
default_transaction_read_only   |           |              | off
default_with_oids               |           |              | off
effective_cache_size            | 1         | 2147483647   | 16384
effective_io_concurrency        | 0         | 1000         | 1
enable_bitmapscan               |           |              | on
enable_hashagg                  |           |              | on
enable_hashjoin                 |           |              | on
enable_indexscan                |           |              | on
enable_material                 |           |              | on
enable_mergejoin                |           |              | on
enable_nestloop                 |           |              | on
enable_seqscan                  |           |              | on
enable_sort                     |           |              | on
enable_tidscan                  |           |              | on
escape_string_warning           |           |              | on
extra_float_digits              | -15       | 3            | 0
from_collapse_limit             | 1         | 2147483647   | 8
fsync                           |           |              | on
full_page_writes                |           |              | on
geqo                            |           |              | on
geqo_effort                     | 1         | 10           | 5
geqo_generations                | 0         | 2147483647   | 0
geqo_pool_size                  | 0         | 2147483647   | 0
geqo_seed                       | 0         | 1            | 0
geqo_selection_bias             | 1.5       | 2            | 2
geqo_threshold                  | 2         | 2147483647   | 12
gin_fuzzy_search_limit          | 0         | 2147483647   | 0
hot_standby                     |           |              | off
ignore_system_indexes           |           |              | off
integer_datetimes               |           |              | on
IntervalStyle                   |           |              | postgres
join_collapse_limit             | 1         | 2147483647   | 8
krb_caseins_users               |           |              | off
krb_srvname                     |           |              | postgres
lc_collate                      |           |              | C
lc_ctype                        |           |              | C
lc_messages                     |           |              |
lc_monetary                     |           |              | C
lc_numeric                      |           |              | C
lc_time                         |           |              | C
listen_addresses                |           |              | localhost
lo_compat_privileges            |           |              | off
local_preload_libraries         |           |              |
log_autovacuum_min_duration     | -1        | 2147483      | -1
log_checkpoints                 |           |              | off
log_connections                 |           |              | off
log_destination                 |           |              | stderr
log_disconnections              |           |              | off
log_duration                    |           |              | off
log_error_verbosity             |           |              | default
log_executor_stats              |           |              | off
log_hostname                    |           |              | off
log_line_prefix                 |           |              |
log_lock_waits                  |           |              | off
log_min_duration_statement      | -1        | 2147483      | -1
log_min_error_statement         |           |              | error
log_min_messages                |           |              | warning
log_parser_stats                |           |              | off
log_planner_stats               |           |              | off
log_rotation_age                | 0         | 35791394     | 1440
log_rotation_size               | 0         | 2097151      | 10240
log_statement                   |           |              | none
log_statement_stats             |           |              | off
log_temp_files                  | -1        | 2147483647   | -1
log_timezone                    |           |              | UNKNOWN
log_truncate_on_rotation        |           |              | off
logging_collector               |           |              | off
maintenance_work_mem            | 1024      | 2097151      | 16384
max_connections                 | 1         | 536870911    | 100
max_files_per_process           | 25        | 2147483647   | 1000
max_function_args               | 100       | 100          | 100
max_identifier_length           | 63        | 63           | 63
max_index_keys                  | 32        | 32           | 32
max_locks_per_transaction       | 10        | 2147483647   | 64
max_prepared_transactions       | 0         | 536870911    | 0
max_stack_depth                 | 100       | 2097151      | 100
max_standby_archive_delay       | -1        | 2147483      | 30000
max_standby_streaming_delay     | -1        | 2147483      | 30000
max_wal_senders                 | 0         | 536870911    | 0
password_encryption             |           |              | on
port                            | 1         | 65535        | 5432
post_auth_delay                 | 0         | 2147483647   | 0
pre_auth_delay                  | 0         | 60           | 0
random_page_cost                | 0         | 1.79769e+308 | 4
search_path                     |           |              | "$user",public
segment_size                    | 131072    | 131072       | 131072
seq_page_cost                   | 0         | 1.79769e+308 | 1
server_encoding                 |           |              | SQL_ASCII
server_version                  |           |              | 9.0.8
server_version_num              | 90008     | 90008        | 90008
session_replication_role        |           |              | origin
shared_buffers                  | 16        | 1073741823   | 1024
silent_mode                     |           |              | off
sql_inheritance                 |           |              | on
ssl                             |           |              | off
ssl_renegotiation_limit         | 0         | 2097151      | 524288
standard_conforming_strings     |           |              | off
statement_timeout               | 0         | 2147483647   | 0
superuser_reserved_connections  | 0         | 536870911    | 3
synchronize_seqscans            |           |              | on
synchronous_commit              |           |              | on
syslog_facility                 |           |              | local0
syslog_ident                    |           |              | postgres
tcp_keepalives_count            | 0         | 2147483647   | 0
tcp_keepalives_idle             | 0         | 2147483647   | 0
tcp_keepalives_interval         | 0         | 2147483647   | 0
temp_buffers                    | 100       | 1073741823   | 1024
temp_tablespaces                |           |              |
TimeZone                        |           |              | UNKNOWN
timezone_abbreviations          |           |              | UNKNOWN
trace_notify                    |           |              | off
trace_recovery_messages         |           |              | log
trace_sort                      |           |              | off
track_activities                |           |              | on
track_activity_query_size       | 100       | 102400       | 1024
track_counts                    |           |              | on
track_functions                 |           |              | none
transaction_isolation           |           |              |
transaction_read_only           |           |              | off
transform_null_equals           |           |              | off
unix_socket_group               |           |              |
unix_socket_permissions         | 0         | 511          | 511
update_process_title            |           |              | on
vacuum_cost_delay               | 0         | 100          | 0
vacuum_cost_limit               | 1         | 10000        | 200
vacuum_cost_page_dirty          | 0         | 10000        | 20
vacuum_cost_page_hit            | 0         | 10000        | 1
vacuum_cost_page_miss           | 0         | 10000        | 10
vacuum_defer_cleanup_age        | 0         | 1000000      | 0
vacuum_freeze_min_age           | 0         | 1000000000   | 50000000
vacuum_freeze_table_age         | 0         | 2000000000   | 150000000
wal_block_size                  | 8192      | 8192         | 8192
wal_buffers                     | 4         | 2147483647   | 8
wal_keep_segments               | 0         | 2147483647   | 0
wal_level                       |           |              | minimal
wal_segment_size                | 2048      | 2048         | 2048
wal_sender_delay                | 1         | 10000        | 200
wal_sync_method                 |           |              | fdatasync
wal_writer_delay                | 1         | 10000        | 200
work_mem                        | 64        | 2097151      | 1024
xmlbinary                       |           |              | base64
xmloption                       |           |              | content
zero_damaged_pages              |           |              | off
(195 rows)

테이블 구조에는 ~ 20-30 개의 열, 몇 개의 외래 키 정수, 문자열, 텍스트, 부울이 있습니다. 인덱스 정의는 INDEX USEd 옆의 위 게시물에 있습니다. (질문에 사용되는 8 개의 색인을 게시했습니다). 더 빠른 업데이트를 위해 몇 가지 색인이 더 있으며 내 응용 프로그램을 선택합니다. 마지막으로 클라우드 DB를 사용하고 있으며 아무것도 변경하지 않았습니다. 나는 인덱스 최적화가 그 최적화에 도달하기 전에 얻을 수있는만큼 좋은지 궁금합니다. 그럼에도 불구하고, 나는 정보로 업데이트 할 것입니다.
xlash

답변:


30

먼저 할 수 있습니까? 당신은 쓰기:

며칠 전 날짜로 updated_at 필드가있는 모든 데이터를 가져오고 싶습니다 .

그러나 당신의 WHERE 상태는 다음과 같습니다.

(날짜 (updated_at)) < (날짜 (지금 ())-7)

그렇지 >않습니까?


인덱스

최적의 성능을 위해 다음을 수행 할 수 있습니다.

  • 인덱스를 분할
  • 인덱스에서 관련없는 행을 제외
  • 업데이트 된 술어를 사용하여 근무 외 시간에 인덱스를 자동으로 다시 작성합니다.

색인은 다음과 같습니다.

CREATE INDEX objects_id_updated_at_idx (updated_at::date DESC NULLS LAST)
WHERE  id BETWEEN 0 AND 999999
AND    updated_at > '2012-10-01 0:0'::timestamp  -- some minimum date

CREATE INDEX objects_id_updated_at_idx (updated_at::date DESC NULLS LAST)
WHERE  id BETWEEN 1000000 AND 1999999
AND    updated_at > '2012-10-01 0:0'::timestamp  -- some minimum date

...

두 번째 조건은 인덱스에서 관련없는 행을 즉시 제외하므로 실제 데이터 분포에 따라 더 작고 빠르게 만들어야합니다. 나의 예비 의견에 따르면, 나는 당신이 더 새로운 것을 원한다고 가정합니다 행 .

이 조건은 또한 테이블에서 허용하고 쿼리에서 제외하려는 NULL 값을 자동으로 제외합니다 updated_at. 시간이 지남에 따라 지수의 유용성이 떨어집니다. 쿼리는 항상 최신 항목을 검색합니다. 업데이트 된 WHERE절을 사용하여 정기적으로 색인을 다시 작성하십시오 . 이를 위해서는 테이블에 배타적 잠금이 필요하므로 업무 외 시간에 수행해야합니다. CREATE INDEX CONCURRENTLY잠금 기간 도 최소화해야합니다.

CREATE INDEX CONCURRENTLY objects_id_up_201211_idx; -- create new idx
DROP INDEX  objects_id_up_201210_idx;  -- then drop old

SO에 대한 관련 답변 :

더 최적화하기 CLUSTER위해 주석에서 언급 한대로 사용할 수 있습니다 . 그러나이를 위해서는 완전한 색인이 필요합니다. 부분 인덱스로는 작동하지 않습니다. 일시적으로 생성합니다 :

CREATE INDEX objects_full_idx (id/1000000, updated_at::date DESC NULLS LAST);

이 전체 인덱스 형식은 위 부분 인덱스의 정렬 순서와 일치합니다.

CLUSTER objects USING objects_full_idx;
ANALYZE objects;

테이블이 물리적으로 다시 작성되므로 시간이 오래 걸립니다. 또한 효과적입니다 VACUUM FULL. 테이블에 독점적 인 쓰기 잠금이 필요하므로 시간 외 근무 시간에도 가능합니다. 다시 한 번 덜 침습적 인 대안이 있습니다 : pg_repack

그런 다음 인덱스를 다시 삭제할 수 있습니다. 일회성 효과입니다. 나는 적어도 한 번 이것을 시도하여 쿼리가 얼마나 많은 이점을 얻었는지 확인합니다. 후속 쓰기 작업으로 인해 효과가 저하됩니다. 상당한 효과가 나타나는 경우, 비업무 시간에이 절차를 반복 할 수 있습니다.

테이블 에 많은 쓰기 작업이 수신되면 이 단계에서 비용과 이점을 고려해야합니다. 많은 업데이트의 설정을 고려 들어 FILLFACTOR보다 낮은 100 않는다는을 하기 전에 당신 CLUSTER.

질문

SELECT count(*)
FROM   objects
WHERE  id BETWEEN 0 AND 999999  -- match conditions of partial index!
AND    updated_at > '2012-10-01 0:0'::timestamp
AND    updated_at::date > (now()::date - 7)

이 관련 답변은 인덱스 파티셔닝에 대한 고급 기술을 제공합니다.

무엇보다도 자동 색인 (재) 창조를위한 예제 코드를 제공합니다.

PostgreSQL 9.2+ 에는 몇 가지 새로운 기능이 있습니다. 인덱스 전용 스캔 만으로도 가치가 있습니다.

autovacuum제대로 작동 하는지 확인하십시오 . CLUSTER귀하가보고 한 큰 이익 은 부분적으로 VACUUM FULL귀하가 얻는 암시 때문일 수 있습니다 CLUSTER. 어쩌면 이것은 Heroku가 자동으로 설정했을 수도 있습니다.
질문의 설정이 좋아 보입니다. 그래서 그것은 아마도 여기서 문제가 아니며 CLUSTER실제로 효과적이었습니다.

선언적 파티셔닝

Postgres 12 에서 마침내 성숙했습니다 . 수동 인덱스 분할 대신 (또는 적어도 추가로) 지금 사용하는 것이 좋습니다. updated_at파티션 키를 사용하여 범위 파티셔닝 . (일반 성능, 빅 데이터 및 특히 btree 인덱스 성능에 대한 여러 가지 개선 사항 외에도)

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