Indexoptimierung mit Datumsangaben


26

Ich habe eine große Tabelle von Objekten (15M + Zeile) in PostgreSQL 9.0.8, für die ich veraltete Felder abfragen möchte.

Ich möchte die Abfrage aus Gründen der Skalierbarkeit und Parallelität durch Millionen teilen und alle Daten mit dem Feld "updated_at" abrufen, das ein Datum vor einigen Tagen aufweist.

Ich habe viele Indizes und Abfragen für eine Million IDs ausprobiert und kann mit Herokus Ronin-Hardware keine Leistung unter 100 Sekunden erzielen.

Ich suche nach Vorschlägen, die ich nicht so effizient wie möglich gemacht habe.

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

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

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

VERSUCH # 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 (für einen Monat veralteter Daten)

 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

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

TRY # 7 (siehe: 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 

VERSUCH # 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 ( Teilindex gemäß Erwins Vorschlag, siehe: 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

VERSUCH # 10 ( CLUSTER , gemäß Erwins Vorschlag).

 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

==> Diese Lösung scheint die beste zu sein. Ich muss gründlich testen, um die Gegenwirkungen in meiner Anwendung zu überprüfen.

DB-Einstellungen:

wähle name, min_val, max_val, boot_val aus 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)

Die Tabellenstruktur enthält ~ 20-30 Spalten, einige Fremdschlüssel, Ganzzahlen, Zeichenfolgen, Text und Boolesche Werte. Die Indexdefinition befindet sich in der obigen Veröffentlichung neben INDEX USEd. (Ich habe die 8 von den Abfragen verwendeten Indizes gepostet.) Ich habe ein paar weitere Indizes für eine schnellere Aktualisierung und Auswahl für meine Anwendung. Schließlich verwende ich eine Cloud-DB und habe nichts geändert. Ich frage mich vor allem, ob meine Indexdefinition so gut ist, wie es nur geht, bevor ich zu dieser Optimierung komme. Trotzdem werde ich mit den Infos updaten.
Flash

Antworten:


30

Zunächst einmal kann es sein? Du schreibst:

Ich möchte alle Daten mit dem Feld updated_at mit einem Datum von vor einigen Tagen abrufen .

Aber Ihr WHEREZustand ist:

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

Sollte das nicht sein >?


Indizes

Für eine optimale Leistung können Sie ...

  • partitionieren Sie Ihre Indizes
  • Ausschluss irrelevanter Zeilen aus den Indizes
  • Indizes werden außerhalb der Geschäftszeiten mit aktualisiertem Vergleichselement automatisch neu erstellt.

Ihre Indizes könnten so aussehen:

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

...

Die zweite Bedingung schließt irrelevante Zeilen sofort aus dem Index aus, wodurch der Index - abhängig von Ihrer tatsächlichen Datenverteilung - kleiner und schneller werden sollte. In Übereinstimmung mit meinem vorläufigen Kommentar gehe ich davon aus, dass Sie neuere Zeilen wünschen .

Die Bedingung schließt auch automatisch NULL-Werte aus, updated_atdie Sie anscheinend in der Tabelle zulassen und offensichtlich in der Abfrage ausschließen möchten. Die Nützlichkeit des Index verschlechtert sich mit der Zeit. Die Abfrage ruft immer die neuesten Einträge ab. Erstellen Sie den Index WHEREregelmäßig mit einer aktualisierten Klausel neu. Dies erfordert eine exklusive Sperre für den Tisch, also tun Sie dies außerhalb der Geschäftszeiten. Es gibt auch, CREATE INDEX CONCURRENTLYum die Dauer der Sperre zu minimieren:

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

Verwandte Antwort auf SO:

Zur weiteren Optimierung können Sie verwenden, CLUSTERwie wir in den Kommentaren erwähnt haben. Dafür benötigen Sie jedoch einen vollständigen Index. Funktioniert nicht mit einem Teilindex. Sie würden vorübergehend erstellen:

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

Diese Form des vollständigen Index entspricht der Sortierreihenfolge der obigen Teilindizes.

CLUSTER objects USING objects_full_idx;
ANALYZE objects;

Dies dauert eine Weile, da die Tabelle physisch neu geschrieben wird. Es ist auch effektiv ein VACUUM FULL. Es benötigt eine exklusive Schreibsperre für den Tisch, also tun Sie dies außerhalb der Geschäftszeiten - vorausgesetzt, Sie können sich das überhaupt leisten. Auch hier gibt es eine weniger invasive Alternative: pg_repack

Sie können den Index dann wieder löschen. Es ist ein einmaliger Effekt. Ich würde dies zumindest einmal versuchen, um zu sehen, wie sehr Ihre Anfragen davon profitieren. Der Effekt verschlechtert sich bei nachfolgenden Schreibvorgängen. Sie können diesen Vorgang auch außerhalb der Geschäftszeiten wiederholen, wenn Sie einen erheblichen Effekt feststellen.

Wenn Ihre Tabelle viele Schreibvorgänge erhält , müssen Sie Kosten und Nutzen für diesen Schritt abwägen. Bei vielen UPDATES sollten Sie einen FILLFACTORniedrigeren Wert als 100 festlegen . Tun Sie dies vor Ihnen CLUSTER.

Abfrage

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)

Mehr

Diese verwandte Antwort enthält eine fortgeschrittenere Technik für die Indexpartitionierung:

Es enthält unter anderem Beispielcode für die automatische Index- (Neu-) Erstellung.

PostgreSQL 9.2+ hat einige neue Funktionen für Sie. Nur Index-Scans würden sich schon lohnen.

Stellen Sie sicher, dass autovacuumdas ordnungsgemäß ausgeführt wird. Der enorme Gewinn, den CLUSTERSie gemeldet haben, ist möglicherweise zum Teil auf das implizite Ergebnis zurückzuführen VACUUM FULL, das Sie erhalten CLUSTER. Vielleicht wird dies von Heroku automatisch eingerichtet, nicht sicher.
Die Einstellungen in Ihrer Frage sehen gut aus. Das ist hier also wahrscheinlich kein Problem und war CLUSTERwirklich so effektiv.

Deklarative Partitionierung

ist endlich in Postgres 12 gereift . Ich würde jetzt in Betracht ziehen, dies anstelle der manuellen Indexpartitionierung (oder zumindest zusätzlich) zu verwenden. Bereichspartitionierung mit updated_atals Partitionsschlüssel. (Neben zahlreichen Verbesserungen der allgemeinen Leistung, insbesondere der Leistung von Big Data und Btree-Indizes.)

Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.