برنامه نویسی

حذف کارآمد داده ها – انجمن DEV

وقتی داده ها را در CockroachDB (CRDB) حذف می کنم، حذف کند است، و چگونه می توانم سرعت آن را افزایش دهم؟

این موضوعی است که گهگاه مطرح می شود، و فوق العاده شهودی نیست، بنابراین فکر می کنم ارزش توضیح بیشتر را دارد.

برپایی

برای بررسی این موضوع، بیایید داده هایی ایجاد کنیم که بتوانیم با آنها بازی کنیم.

ابتدا بیایید یک پایگاه داده ایجاد کنیم و سپس پایگاه داده را طوری پیکربندی کنیم که پس از حذف امروز فقط 60 ثانیه صبر کنیم تا در واقع داده ها را از لایه ذخیره سازی در CRDB حذف کنیم (اگر لازم باشد چندین بار داده ها را حذف کنیم و همه چیز را دوباره اجرا کنیم مفید خواهد بود)

CREATE DATABASE deletes;
ALTER DATABASE deletes CONFIGURE ZONE USING gc.ttlseconds= 60;
USE deletes;
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

سپس، بیایید یک جدول ایجاد کنیم. من می خواهم جدولی با چند ستون از کاردینالیته های مختلف و 100 میلیون ردیف ایجاد کنم. یک تابع مفید در CRDB به نام وجود دارد generate_series که برای این نوع ایجاد داده عالی است:

CREATE TABLE deletes.order_items ( id PRIMARY KEY, customer_id, order_id, product_id, order_date )
AS
SELECT
  g.i AS id,
  g.i % 77777 AS customer_id,
  g.i % 1000000 as order_id,
  g.i % 11111 as product_id,
  g.i::TIMESTAMP as order_date
FROM generate_series(1578003200, 1678003200, 1) g(i);
-- 100,000,000 records
--  between 2020-01-02 22:13:20 (i.e., 1578003200)
--  and 2023-03-05 08:00:00 (i.e., 1678003200)
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

من از چند تکنیک مفید در اینجا استفاده می کنم:

  • من از CREATE TABLE AS (یا به قول ما CTAS) برای ایجاد جدول جدید. این در CRDB بسیار سریعتر از ایجاد جدول و سپس انجام آن است INSERT INTO ... SELECT زیرا تمام الگوریتم های بررسی سازگاری را دور می زند.
  • من از g.i مقدار را به عنوان “مقدار اولیه” من و استفاده از آن مستقیماً به عنوان فیلد شناسه من
  • برای برخی از فیلدهای دیگر، من از عملگر مدول (یعنی ٪) در برابر gi feild برای ایجاد مقادیر کم کاردینالیته استفاده می کنم.
  • من مقادیری در محدوده بین 1578003200 و 1678003200 ایجاد می کنم تا بتوانم به راحتی این مقادیر را به مهر زمانی تبدیل کنم.
  • نکته: برای اینکه بفهمید از چه مقادیری می خواهید به عنوان مقادیر شروع و پایان استفاده کنید، اجرا کنید SELECT '2023-01-15 2:00 PM'::TIMESTAMP::INT; با مقادیر تاریخ مورد نظر خود، مقادیر مورد نظر را مشخص کنید.

هنگامی که جدول خود را ایجاد کردیم، بیایید نمایه هایی را در فیلدهای دیگر خود ایجاد کنیم تا آنچه را که یک جدول واقعی ممکن است انجام دهد تقلید کنیم:

CREATE INDEX ON order_items ( customer_id );
CREATE INDEX ON order_items ( order_id );
CREATE INDEX ON order_items ( product_id );
CREATE INDEX ON order_items ( order_date );
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

برای درک درستی فیلدهای خود، می توانیم یک پرس و جو را اجرا کنیم:

SELECT
  COUNT(*) AS rec_count,
  COUNT(DISTINCT customer_id) AS cust_count,
  COUNT(DISTINCT order_id) AS ord_count,
  COUNT(DISTINCT product_id) AS prd_count,
  COUNT(DISTINCT order_date) AS date_count
FROM order_items;
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

نتایج ما:

  rec_count | cust_count | ord_count | prd_count | date_count
------------+------------+-----------+-----------+-------------
  100000001 |      77777 |   1000000 |     11111 |  100000001
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

نگاهی به توضیح طرح ها

اکنون که داده‌هایی برای بازی کردن داریم، می‌توانیم بفهمیم وقتی داده‌های این جدول را حذف می‌کنیم چه اتفاقی می‌افتد.

بیایید با انتخاب یک رکورد واحد با مقدار PK شروع کنیم:

SELECT *
FROM order_items
WHERE id = 1600000000;

      id     | customer_id | order_id | product_id |     order_date
-------------+-------------+----------+------------+----------------------
  1600000000 |       49333 |        0 |       4889 | 2020-09-13 12:26:40
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

اکنون، بیایید “EXPLAIN” را به درخواست خود اضافه کنیم تا به طرح اجرا نگاه کنیم.

EXPLAIN
SELECT *
FROM order_items
WHERE id = 1600000000;
                                                        info
---------------------------------------------------------------------------------------------------------------------
  distribution: local
  vectorized: true

   scan
    estimated row count: 1 (<0.01% of the table; stats collected 3 hours ago; using stats forecast for 3 hours ago)
    table: order_items@order_items_pkey
    spans: [/1600000000 - /1600000000]
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

این به همان سادگی یک برنامه اجرایی است که می توانید دریافت کنید. این در حال انجام اسکن 1 دهانه در شاخص اولیه (یعنی خود جدول) است.

اکنون، بیایید با فیلد order_date پرس و جو کنیم که همچنین یک مقدار بسیار بالا با یک شاخص روی آن است:

SELECT *
FROM order_items
WHERE order_date = 1600000000::timestamp;
      id     | customer_id | order_id | product_id |     order_date
-------------+-------------+----------+------------+----------------------
  1600000000 |       49333 |        0 |       4889 | 2020-09-13 12:26:40
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

ما دقیقا همان داده ها را پس می گیریم. بیایید به توضیح طرح نگاه کنیم:

EXPLAIN
SELECT *
FROM order_items
WHERE order_date = 1600000000::timestamp;
                                                                            info
-------------------------------------------------------------------------------------------------------------------------------------------------------------
  distribution: local
  vectorized: true

   index join
   estimated row count: 1
   table: order_items@order_items_pkey
  
  └──  scan
        estimated row count: 1 (<0.01% of the table; stats collected 3 hours ago; using stats forecast for 3 hours ago)
        table: order_items@order_items_order_date_idx
        spans: [/'2020-09-13 12:26:40' - /'2020-09-13 12:26:40']

  index recommendations: 1
  1. type: index replacement
     SQL commands: CREATE INDEX ON order_items (order_date) STORING (customer_id, order_id, product_id); DROP INDEX order_items@order_items_order_date_idx;
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

به چند تفاوت در مورد این طرح توضیحی با طرح بالا توجه کنید:

  1. ما یک اسکن از آن انجام می دهیم order_items_order_date_idx این بار
  2. سپس یک را انجام می دهیم index join بازگشت به شاخص اولیه order_items_pkey
  3. CRDB به کمک ما چند پیشنهاد در مورد اینکه چگونه می توانیم شاخص خود را برای عملکرد بهتر تغییر دهیم، به ما ارائه کرده است.

استفاده از شاخص order_date بسیار کارآمد است زیرا محمول ما روی order_date است. اما فهرست order_date فقط شامل فیلدی است که ما ایندکس می‌کنیم (order_date) و یک مرجع به PK (id). به منظور برآورده کردن پرس و جو ما SELECT * بند، ما باید دوباره به شاخص اولیه ملحق شویم تا بقیه مقادیر فیلد را در این رکورد داشته باشیم. پیشنهاد ایندکس به ما می‌گوید که اگر همه فیلدهای دیگر را از رکورد در فهرست order_date بگنجانیم/ذخیره کنیم، می‌توانیم عضویت ایندکس را رد کنیم، زیرا از قبل تمام نقاط داده‌ای لازم برای برآورده کردن درخواست خود را خواهیم داشت.

شما می توانید این را با تغییر پرس و جوی SELECT ثابت کنید تا فقط فیلدهای id و order_date را برگردانید:

EXPLAIN
SELECT id, order_date
FROM order_items
WHERE order_date = 1600000000::timestamp;
                                                        info
---------------------------------------------------------------------------------------------------------------------
  distribution: local
  vectorized: true

   scan
    estimated row count: 1 (<0.01% of the table; stats collected 3 hours ago; using stats forecast for 3 hours ago)
    table: order_items@order_items_order_date_idx
    spans: [/'2020-09-13 12:26:40' - /'2020-09-13 12:26:40']
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

حرکت به سمت حذف ها

اکنون که حس تفسیر طرح های پرس و جو را روی میز خود داریم، بیایید به این فکر کنیم که وقتی یک رکورد را در CRDB حذف می کنیم چه اتفاقی می افتد.

هنگامی که یک رکورد را با مقدار PK حذف می کنیم، برنامه اجرایی ما بسیار ساده است – رکورد را پیدا کرده و آن را حذف کنید:

EXPLAIN
DELETE FROM order_items
WHERE id = 1600000000;
                                                          info
-------------------------------------------------------------------------------------------------------------------------
  distribution: local
  vectorized: true

   delete
   from: order_items
   auto commit
  
  └──  scan
        estimated row count: 1 (<0.01% of the table; stats collected 3 hours ago; using stats forecast for 3 hours ago)
        table: order_items@order_items_pkey
        spans: [/1600000000 - /1600000000]
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

اگر یک پرس و جوی مشابه DELETE را با استفاده از فیلد order_date به جای PK انجام دهیم، می بینیم که یک بار دیگر یک index join داریم:

EXPLAIN DELETE FROM order_items
WHERE order_date = 1600000000::timestamp;
                                                                            info
-------------------------------------------------------------------------------------------------------------------------------------------------------------
  distribution: local
  vectorized: true

   delete
   from: order_items
   auto commit
  
  └──  index join
       estimated row count: 1
       table: order_items@order_items_pkey
      
      └──  scan
            estimated row count: 1 (<0.01% of the table; stats collected 3 hours ago; using stats forecast for 3 hours ago)
            table: order_items@order_items_order_date_idx
            spans: [/'2020-09-13 12:26:40' - /'2020-09-13 12:26:40']

  index recommendations: 1
  1. type: index replacement
     SQL commands: CREATE INDEX ON order_items (order_date) STORING (customer_id, order_id, product_id); DROP INDEX order_items@order_items_order_date_idx;
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

نیاز به این پیوستن شاخص در کوئری SELECT کاملاً واضح است، اما چرا در کوئری DELETE به آن نیاز است؟ پاسخ این است که وقتی CRDB یک رکورد را حذف می‌کند، باید ورودی(های) را از نمایه اصلی حذف کند، اما همچنین باید ورودی(های) را از تمام نمایه‌های مربوطه حذف کند. و برای حذف ورودی‌های فهرست، به کلیدهای فهرست نیاز دارد.

بیایید توصیه های بهینه ساز را دنبال کنیم و این شاخص “پوشش” جدید را ایجاد کنیم:

CREATE INDEX ON order_items (order_date) STORING (customer_id, order_id, product_id);
DROP INDEX order_items@order_items_order_date_idx;                                                                                                                         
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

اگر دستور DELETE خود را دوباره با این شاخص جدید اجرا کنیم، می بینیم که نیاز به پیوستن به فهرست حذف شده است.

EXPLAIN DELETE FROM order_items
WHERE order_date = 1600000000::timestamp;                                                                                                  info
-------------------------------------------------------------------------------------------------------------------------
  distribution: local
  vectorized: true

   delete
   from: order_items
   auto commit
  
  └──  scan
        estimated row count: 1 (<0.01% of the table; stats collected 3 hours ago; using stats forecast for 3 hours ago)
        table: order_items@order_items_order_date_idx1
        spans: [/'2020-09-13 12:26:40' - /'2020-09-13 12:26:40']
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

هشدار: این یک تکنیک عالی برای کمک به حذف کارآمد است. اگر به طور منظم داده‌های قدیمی را حذف دسته‌ای انجام می‌دهید یا سوابقی را پاکسازی می‌کنید که دارای وضعیت «حذف‌پذیر» هستند، این می‌تواند بسیار کمک کند – به خصوص در جداول واقعاً بزرگ. با این حال، توجه داشته باشید که اگر بعداً نمایه دیگری را در جدول order_items اضافه کنید، باید به خاطر داشته باشید که فیلد تازه فهرست‌شده را در فهرستی که قرار است در حذف‌های شما به کار گرفته شود، بگنجانید – در غیر این صورت، این پیوستن به فهرست خزش می‌کند. به برنامه خود برگردید

خلاصه

نمایه های پوششی ابزاری عالی در CRDB و سایر سیستم های DBMS برای کارآمدتر کردن پرس و جوهای خاص هستند. تشخیص اینکه چه زمانی باید برای کمک به پرس‌و‌جوهای SELECT استفاده شوند، نسبتاً آسان است، اما درک اینکه آنها می‌توانند به DELETE‌ها نیز کمک کنند، کمی غیر شهودی است. اما اکنون می دانید، حذف بسیار خوشحال کننده است!!

نوشته های مشابه

دیدگاهتان را بنویسید

نشانی ایمیل شما منتشر نخواهد شد. بخش‌های موردنیاز علامت‌گذاری شده‌اند *

دکمه بازگشت به بالا