Как заставить Postgres возвращать разумное количество строк, не блокируя всю таблицу? - PullRequest
0 голосов
/ 20 февраля 2019

Недавно мы перенесли большое приложение и базу данных в Heroku из EngineYard.Наша новая база данных имеет меньше ресурсов, чем база данных EngineYard, и выставила несколько запросов, которые, вероятно, работали раньше только из-за большого количества железа.

Одна особенно неприятная проблема - наша неспособность возвращать значения из большой таблицы.Эта таблица является самой большой в приложении и содержит более 1 миллиарда строк.У некоторых клиентов будут строки в десятки тысяч, а у некоторых может быть сто миллионов.

Таблица индексируется с помощью subscription_id и status.

Запрос прост:

select count(*)
from my_large_table
where subscription_id = 123
  and status = 'Valid'

К сожалению, этот запрос, похоже, выполняет ExclusiveLock для таблицы, в результате чего запросы помещаются в очередь.Для клиента с 5 миллионами строк данных запрос должен быть убит после оттока в течение часа.Этот запрос буквально останавливает наше приложение.

Однако нам нужно знать количество в этой таблице для каждого клиента и статуса.Это не должно быть мертвым.Но это не может быть полная выдумка, подобная процедуре count_estimate, которая на самом деле просто выдает вымышленные числа.

Я уверен, что для этого есть решение.Что я могу сделать, чтобы получить этот счет?Есть ли какой-нибудь способ сохранить его от блокировки?

Таблица огромна и имеет множество атрибутов.Я перебрал значения citext назад, когда проектировал их, потому что перешел из MySQL, где я привык к поиску без учета регистра и принял их как должное.Мне действительно нужно только citext на 4 вершинах столбцов (attribute1, attribute2).Это действительно названия полей, а не запутывания.Таблица является целью для различных типов данных на основе значения standard_id.

Я ценю помощь.

/*
 Navicat PostgreSQL Data Transfer

 Source Server         : Heroku myapp-production
 Source Server Version : 100600
 Source Host           : ec2-34-196-135-106.compute-1.amazonaws.com
 Source Database       : d6hrvd8r3u28t0
 Source Schema         : public

 Target Server Version : 100600
 File Encoding         : utf-8

 Date: 02/20/2019 09:37:49 AM
*/

-- ----------------------------
--  Table structure for apps
-- ----------------------------
DROP TABLE IF EXISTS "public"."apps";
CREATE TABLE "public"."apps" (
    "id" int8 NOT NULL DEFAULT nextval('apps_id_seq'::regclass),
    "attribute1" "public"."citext" COLLATE "default",
    "attribute2" "public"."citext" COLLATE "default",
    "attribute3" "public"."citext" COLLATE "default",
    "attribute4" "public"."citext" COLLATE "default",
    "attribute5" "public"."citext" COLLATE "default",
    "attribute6" "public"."citext" COLLATE "default",
    "attribute7" "public"."citext" COLLATE "default",
    "attribute8" "public"."citext" COLLATE "default",
    "attribute9" "public"."citext" COLLATE "default",
    "attribute10" "public"."citext" COLLATE "default",
    "attribute11" "public"."citext" COLLATE "default",
    "attribute12" "public"."citext" COLLATE "default",
    "attribute13" "public"."citext" COLLATE "default",
    "attribute14" "public"."citext" COLLATE "default",
    "attribute15" "public"."citext" COLLATE "default",
    "attribute16" "public"."citext" COLLATE "default",
    "attribute17" "public"."citext" COLLATE "default",
    "attribute18" "public"."citext" COLLATE "default",
    "attribute19" "public"."citext" COLLATE "default",
    "attribute20" "public"."citext" COLLATE "default",
    "attribute21" "public"."citext" COLLATE "default",
    "attribute22" "public"."citext" COLLATE "default",
    "attribute23" "public"."citext" COLLATE "default",
    "attribute24" "public"."citext" COLLATE "default",
    "attribute25" "public"."citext" COLLATE "default",
    "attribute26" "public"."citext" COLLATE "default",
    "attribute27" "public"."citext" COLLATE "default",
    "attribute28" "public"."citext" COLLATE "default",
    "attribute29" "public"."citext" COLLATE "default",
    "attribute30" "public"."citext" COLLATE "default",
    "attribute31" "public"."citext" COLLATE "default",
    "attribute32" "public"."citext" COLLATE "default",
    "attribute33" "public"."citext" COLLATE "default",
    "attribute34" "public"."citext" COLLATE "default",
    "attribute35" "public"."citext" COLLATE "default",
    "attribute36" "public"."citext" COLLATE "default",
    "attribute37" "public"."citext" COLLATE "default",
    "attribute38" "public"."citext" COLLATE "default",
    "attribute39" "public"."citext" COLLATE "default",
    "attribute40" "public"."citext" COLLATE "default",
    "attribute41" "public"."citext" COLLATE "default",
    "attribute42" "public"."citext" COLLATE "default",
    "attribute43" "public"."citext" COLLATE "default",
    "attribute44" "public"."citext" COLLATE "default",
    "attribute45" "public"."citext" COLLATE "default",
    "attribute46" "public"."citext" COLLATE "default",
    "attribute47" "public"."citext" COLLATE "default",
    "attribute48" "public"."citext" COLLATE "default",
    "attribute49" "public"."citext" COLLATE "default",
    "attribute50" "public"."citext" COLLATE "default",
    "created_at" timestamp(6) NOT NULL,
    "updated_at" timestamp(6) NOT NULL,
    "standard_id" int4 NOT NULL,
    "status" "public"."citext" COLLATE "default",
    "listing_id" int4 NOT NULL,
    "repository_id" int4 NOT NULL,
    "subscription_id" int4 NOT NULL,
    "attribute_info" "public"."hstore",
)
WITH (OIDS=FALSE);
ALTER TABLE "public"."apps" OWNER TO "ufn67drbuner1e";

-- ----------------------------
--  Primary key structure for table apps
-- ----------------------------
ALTER TABLE "public"."apps" ADD PRIMARY KEY ("id") NOT DEFERRABLE INITIALLY IMMEDIATE;

-- ----------------------------
--  Indexes structure for table apps
-- ----------------------------
CREATE INDEX  "app_listing_idx" ON "public"."apps" USING btree(listing_id "pg_catalog"."int4_ops" ASC NULLS LAST);
CREATE INDEX  "app_subscription_idx" ON "public"."apps" USING btree(subscription_id "pg_catalog"."int4_ops" ASC NULLS LAST);
CREATE UNIQUE INDEX  "apps_listing_idx" ON "public"."apps" USING btree(listing_id "pg_catalog"."int4_ops" ASC NULLS LAST, "id" "pg_catalog"."int8_ops" ASC NULLS LAST);
CREATE INDEX  "apps_repository_idx" ON "public"."apps" USING btree(repository_id "pg_catalog"."int4_ops" ASC NULLS LAST, subscription_id "pg_catalog"."int4_ops" ASC NULLS LAST);
CREATE INDEX  "listing_and_attr_idx" ON "public"."apps" USING btree(listing_id "pg_catalog"."int4_ops" ASC NULLS LAST, attribute1 COLLATE "default" "public"."citext_ops" ASC NULLS LAST, attribute2 COLLATE "default" "public"."citext_ops" ASC NULLS LAST, attribute3 COLLATE "default" "public"."citext_ops" ASC NULLS LAST);
CREATE INDEX  "listing_and_attr_idx" ON "public"."apps" USING btree(listing_id "pg_catalog"."int4_ops" ASC NULLS LAST, attribute1 COLLATE "default" "public"."citext_ops" ASC NULLS LAST, attribute2 COLLATE "default" "public"."citext_ops" ASC NULLS LAST, attribute3 COLLATE "default" "public"."citext_ops" ASC NULLS LAST);
CREATE INDEX  "listing_and_attr_idx" ON "public"."apps" USING btree(listing_id "pg_catalog"."int4_ops" ASC NULLS LAST, attribute1 COLLATE "default" "public"."citext_ops" ASC NULLS LAST, attribute2 COLLATE "default" "public"."citext_ops" ASC NULLS LAST, attribute3 COLLATE "default" "public"."citext_ops" ASC NULLS LAST);

1 Ответ

0 голосов
/ 20 февраля 2019

О блокировке: она не вызвана запросом SELECT, который вы выполняете.

Единственное объяснение, которое я имею, состоит в том, что он выполняется в транзакции, которая сделала что-то еще, что вызвало EXCLUSIVE LOCK чтобы быть взятым.

Единственная хорошая теория - это REFRESH MATERIALIZED VIEW CONCURRENTLY Чем выполнялась в той же транзакции.Другие вещи, такие как блокировки расширения отношений (не удерживаются на время транзакции) или ALTER TYPE ... ADD VALUE (который блокирует только другие подобные утверждения), не похожи на вероятных подозреваемых.

Я не знаю, какие компании Herokuвстроен в их PostgreSQL, но держу пари, что это не было EXCLUSIVE LOCK на SELECT.

Но даже без этой ложной блокировки подсчет количества строк в таблице медленный и ресурсоемкий.

Если обычные оценки (pg_stat_get_live_tuples() и pg_class.reltuples) недостаточно хороши, вы можете использовать триггер:

CREATE TABLE row_counter (
   reloid oid PRIMARY KEY,
   count bigint NOT NULL
);

CREATE FUNCTION count_trig() RETURNS trigger
   LANGUAGE plpgsql AS
$$BEGIN
   IF TG_OP = 'INSERT' THEN
      UPDATE row_counter
      SET count = count + 1
      WHERE reloid = TG_RELID;

      RETURN NEW;
   ELSIF TG_OP = 'DELETE' THEN
      UPDATE row_counter
      SET count = count - 1
      WHERE reloid = TG_RELID;

      RETURN OLD;
   END IF;
END;$$;

CREATE TRIGGER count_trig AFTER INSERT OR DELETE ON my_large_table
   FOR EACH ROW EXECUTE PROCEDURE count_trig();

Вам просто нужно инициализировать таблицу в какой-то момент.

Триггер уровня оператора для TRUNCATE оставлен читателю в качестве упражнения.

...