Pengoptimalan indeks dengan tanggal

27

Saya memiliki tabel besar objek (15M + baris) di PostgreSQL 9.0.8, yang ingin saya tanyakan untuk bidang yang sudah usang.

Saya ingin membagi kueri dengan jutaan, untuk tujuan skalabilitas & konkurensi, dan saya ingin mengambil semua data dengan bidang updated_at dengan tanggal beberapa hari yang lalu.

Saya telah mencoba banyak indeks, dan permintaan, dengan sejuta id, dan sepertinya saya tidak bisa mendapatkan kinerja di bawah 100 detik dengan perangkat keras Ronin Heroku.

Saya mencari saran yang belum saya coba seefisien ini.

COBA # 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

COBA # 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

COBA # 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

MENCOBA # 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

MENCOBA # 5 (untuk satu bulan data yang kedaluwarsa)

 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

MENCOBA # 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

COBALAH # 7 (lihat: 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 

COBA # 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

MENCOBA # 9 ( sebagian indeks sesuai saran Erwin, lihat: 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

COBALAH # 10 ( CLUSTER , sesuai saran Erwin).

 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

==> Solusi ini tampaknya menjadi yang menang. Saya harus menguji secara menyeluruh untuk memverifikasi dampak yang berlawanan di semua aplikasi saya.

Pengaturan DB:

pilih nama, min_val, max_val, boot_val dari pg_settings;

             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)
xlash
sumber
Struktur tabel memiliki ~ 20-30 kolom, bilangan bulat kunci asing, string, teks, boolean. Definisi indeks ada dalam posting di atas di sebelah INDEX USEd. (Saya memposting 8 indeks yang digunakan oleh kueri). Saya memiliki beberapa indeks lagi untuk pembaruan yang lebih cepat dan memilih aplikasi saya. Akhirnya, saya menggunakan DB cloud, dan saya belum mengubah apa pun. Saya bertanya-tanya sebagian besar apakah definisi indeks saya sebaik yang bisa didapat sebelum mendapatkan optimasi tersebut. Meskipun demikian, saya akan memperbarui dengan info.
xlash

Jawaban:

30

Pertama, mungkinkah? Anda menulis:

Saya ingin mengambil semua data dengan bidang updated_at dengan tanggal beberapa hari yang lalu .

Tetapi WHEREkondisi Anda adalah:

(date (updated_at)) < (date (now ()) - 7)

Bukankah seharusnya begitu >?


Indeks

Untuk kinerja optimal , Anda dapat ...

  • mempartisi indeks Anda
  • mengecualikan baris yang tidak relevan dari indeks
  • secara otomatis membuat ulang indeks di luar jam kerja dengan predikat yang diperbarui.

Indeks Anda dapat terlihat seperti:

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

...

Kondisi kedua mengecualikan baris yang tidak relevan dari indeks segera, yang seharusnya membuatnya lebih kecil dan lebih cepat - tergantung pada distribusi data Anda yang sebenarnya. Sesuai dengan komentar awal saya, saya berasumsi Anda ingin baris yang lebih baru .

Kondisi ini juga secara otomatis mengecualikan nilai NULL di updated_at- yang tampaknya Anda izinkan dalam tabel dan jelas ingin dikecualikan dalam kueri. Kegunaan indeks memburuk dari waktu ke waktu. Permintaan selalu mengambil entri terbaru. Buat kembali indeks dengan WHEREklausa yang diperbarui secara berkala. Ini membutuhkan kunci eksklusif di atas meja, jadi lakukan itu di luar jam. Ada juga CREATE INDEX CONCURRENTLYuntuk meminimalkan durasi kunci:

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

Jawaban terkait pada SO:

Untuk lebih mengoptimalkan, Anda bisa menggunakan CLUSTERseperti yang kami sebutkan di komentar. Tetapi Anda perlu indeks lengkap untuk itu. Tidak berfungsi dengan indeks parsial. Anda akan membuat sementara:

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

Bentuk indeks lengkap ini cocok dengan urutan sortir dari indeks parsial di atas.

CLUSTER objects USING objects_full_idx;
ANALYZE objects;

Ini akan memakan waktu cukup lama, karena meja ditulis ulang secara fisik. Ini juga efektif a VACUUM FULL. Ini membutuhkan kunci tulis eksklusif di atas meja, jadi lakukan di luar jam kerja - asalkan Anda mampu melakukannya sama sekali. Sekali lagi, ada alternatif yang kurang invasif: pg_repack

Anda kemudian dapat menjatuhkan indeks lagi. Ini efek satu kali. Saya setidaknya akan mencoba ini sekali untuk melihat seberapa besar permintaan Anda mendapat manfaat darinya. Efeknya memburuk dengan operasi penulisan selanjutnya. Anda dapat mengulangi prosedur ini di luar jam kerja jika Anda melihat efek yang cukup besar.

Jika meja Anda menerima banyak operasi penulisan, Anda harus mempertimbangkan biaya dan manfaat untuk langkah ini. Untuk banyak UPDATE, pertimbangkan untuk menetapkan yang FILLFACTORlebih rendah dari 100. Lakukan itu sebelum Anda CLUSTER.

Pertanyaan

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)

Lebih

Jawaban terkait ini menyajikan teknik yang lebih canggih untuk mempartisi indeks:

Antara lain menyediakan contoh kode untuk pembuatan indeks otomatis (re-).

PostgreSQL 9.2+ memiliki beberapa fitur baru untuk Anda. Pemindaian hanya indeks saja akan membuatnya bernilai saat Anda.

Pastikan itu autovacuumberjalan dengan baik. Keuntungan besar yang CLUSTERAnda laporkan mungkin sebagian disebabkan oleh implisit VACUUM FULLyang Anda dapatkan CLUSTER. Mungkin ini diatur oleh Heroku secara otomatis, tidak yakin.
Pengaturan dalam pertanyaan Anda terlihat bagus. Jadi itu mungkin bukan masalah di sini dan CLUSTERbenar-benar efektif.

Partisi deklaratif

akhirnya jatuh tempo di Postgres 12 . Saya akan mempertimbangkan menggunakannya sekarang daripada mempartisi indeks secara manual (atau setidaknya tambahan). Rentang partisi dengan updated_atsebagai tombol partisi. (Selain beberapa peningkatan kinerja umum, data besar dan kinerja indeks btree khususnya.)

Erwin Brandstetter
sumber