همانطور که می دانید، IOT Deviceها به طور پیوسته جریانی از داده را به سمت دیتابیس هدایت می کنند به طوری که درج این داده ها در دیتابیس رابطه ای می تواند با چالشهای پرفورمنسی همراه باشد.
در اوراکل نسخه 19c ، راهکاری برای حل این مسئله پرفورمنسی ارائه شد که مطابق با آن، این دسته از دیتا ابتدأَ در حافظه ثبت شده و در نهایت به صورت دسته ای به دیسک منتقل می شوند. این قابلیت جدید، Memoptimized Rowstore Fast Ingest نام دارد.
البته به این ویژگی اصطلاحا deferred insert هم گفته می شود چرا که ابتدا دیتا در قسمتی از large pool ثبت می شود و بعد از گذشت مدت زمانی، به صورت خودکار توسط اوراکل به دیسک منتقل شده و در زمانی که دیتا در Large Pool قرار دارد، توسط sessionهای دیگر قابل رویت نخواهد بود.
توجه: استفاده از ویژگی Fast Ingest برای امور متداول در دیتابیسهای OLTP، به هیچ وجه توصیه نمی شود چرا که در شرایطی چون کرش کردن instance و یا پر شدن فضا tablespaceها ممکن است Data loss رخ دهد.
فعالسازی و استفاده از Fast Ingest
قابلیت Fast Ingest در محیط Cloud و Exadata قابل استفاده است بنابرین برای استفاده از این ویژگی در محیط on-premise، باید پارامتر exadata_feature_on را فعال کرد:
alter system set “_exadata_feature_on”=true scope=spfile;
در غیر این صورت، با خطای زیر مواجه خواهیم شد:
ORA-12754: Feature ‘Memoptimized Rowstore’ is disabled due to missing capability ‘Runtime Environment’.
بروزرسانی: در نسخه 21c و 23c نیازی به تنظیم این پارامتر نیست.
برای استفاده از ویژگی Fast Ingest، باید خصیصه MEMOPTIMIZE FOR WRITE را در سطح جدول تنظیم کنیم. این کار در زمان ایجاد جدول و یا بعد از ایجاد آن(از طریق دستور alter table) قابل انجام است:
SQL> create table TBL1
2 (
3 id NUMBER not null,
4 text VARCHAR2(3000)
5 )
6 MEMOPTIMIZE FOR WRITE;
ORA-62145: MEMOPTIMIZE FOR WRITE feature not allowed on segment with deferred storage.
همانطور که می بینید، صرف استفاده از خصیصه MEMOPTIMIZE FOR WRITE کافی نخواهد بود و ایجاد فوری SEGMENT هم برای استفاده از این ویژگی ضروری می باشد این کار با تنظیم پارامتر deferred_segment_creation به مقدار false و یا استفاده از عبارت SEGMENT CREATION IMMEDIATE در متن دستور ایجاد جدول، انجام خواهد شد:
SQL> create table TBL1
2 (
3 id NUMBER not null,
4 text VARCHAR2(3000)
5 )
6 SEGMENT CREATION IMMEDIATE
7 MEMOPTIMIZE FOR WRITE;
Table created
در صورتی که جدول قبلا ایجاد شده، می توان از طریق دستور زیر، خصیصه MEMOPTIMIZE FOR WRITE را برای جدول تنظیم کرد:
SQL> ALTER TABLE tbl1 MEMOPTIMIZE FOR WRITE;
Table altered
ویژگی Fast Ingest در سطح جدول فعال شد اما این کافی نیست و در شرایط فعلی، تمامی insertها بر روی جدول tbl1 به صورت Conventional اجرا خواهند شد!!!
برای درج اطلاعات به روش Fast Ingest، باید از hintای به نام MEMOPTIMIZE_WRITE استفاده کرد:
INSERT /*+ MEMOPTIMIZE_WRITE */ INTO tbl1 VALUES (1,’aaaaaa’);
توجه: غیرفعال کردن قابلیت Fast Ingest برای یک جدول، با دستور زیر انجام می شود:
SQL> ALTER TABLE tbl1 no MEMOPTIMIZE FOR WRITE;
Table altered
مقایسه پرفورمنسی بین Fast Ingest insert و Conventional insert
در قطعه کد زیر سعی داریم ده میلیون رکورد را به صورت متداول در جدول tbl1 درج کنیم، این کار چند ثانیه طول می کشد؟
SQL> begin
2 for i in 1..10111000
3 loop
4 insert /*+Conventional insert*/ into usef.tbl1 values (i, ‘abcdefg’);
5 end loop;
6 commit;
8 end;
9 /
PL/SQL procedure successfully completed.
Elapsed: 00:07:08.26
SQL> select count(*) from tbl1;
COUNT(*)
———-
10111000
درج رکوردها در مدت زمان 7 دقیقه انجام شده است و البته بعد از این مدت زمان نسبتا طولانی، رکوردها توسط تمامی sessionها قابلی رویت هستند و با crash کردن instance دیتایی از بین نخواهد رفت.
همین کار را این بار با استفاده از ویژگی Fast Ingest تکرار می کنیم:
SQL> truncate table usef.tbl1;
Table truncated
SQL> ALTER TABLE tbl1 MEMOPTIMIZE FOR WRITE;
Table altered
SQL> begin
2 for i in 1..10111000
3 loop
4 insert /*+ MEMOPTIMIZE_WRITE */ into usef.tbl1 values (i, ‘abcdefg’);
5 end loop;
6 commit;
8 end;
9 /
PL/SQL procedure successfully completed.
Elapsed: 00:01:09.80
SQL> select count(*) from usef.tbl1;
COUNT(*)
———-
10038692
همانطور که می بینید، با روش Fast Ingest، عملیات درج اطلاعات با سرعت بسیار بالای انجام شده و البته درصدی از اطلاعات هنوز در Large Pool قرار دارند که در آینده به صورت خودکار توسط اوراکل به دیسک منتقل خواهند شد البته اگر قبل از این انتقال، instance به هر دلیلی crash کند، این میزان از اطلاعات از دست خواهند رفت(حدود 72308 رکورد). برای انتقال دستی این اطلاعات به دیسک، می توان دستور زیر استفاده کرد:
SQL> begin
2 dbms_memoptimize_admin.writes_flush();
3 end;
4 /
PL/SQL procedure successfully completed
Executed in 0.015 seconds
با اجرای این پروسیجر، 72308 رکورد باقیمانده در حافظه هم به دیسک منتقل خواهند شد:
SQL> select count(*) from usef.tbl1;
COUNT(*)
———-
10111000
بنابرین شاید بهتر باشد که این پروسیجر را به قطعه کد فوق اضافه کنیم:
SQL>truncate table usef.tbl1;
Table truncated
SQL>begin
for i in 1..10111000
loop
insert /*+ MEMOPTIMIZE_WRITE */ into usef.tbl1 values (i, ‘abcdefg’);
end loop;
commit;
dbms_memoptimize_admin.writes_flush();
end;
/
Elapsed: 00:01:18.86
SQL> select count(*) from usef.tbl1;
COUNT(*)
———-
10111000
فضایی که برای memoptimize_write_area مصرف شده را با پرس و جوی زیر می بینیم:
select round(total_size/1024/1024) total_size_MB,round(used_space/1024/1024) used_space_MB,round(free_space/1024/1024) free_space_MB from v$memoptimize_write_area;
نکات پایانی:
1.اگر اطلاعاتی از جدول در Large Pool موجود باشد، عملیات truncate، صرفا بر روی قسمتی از اطلاعات جدول که در دیسک موجود هستند، انجام خواهد شد:
SQL> truncate table usef.tbl1;
Table truncated.
SQL> insert /*+ MEMOPTIMIZE_WRITE */into usef.tbl1 values (1, ‘abcdefg’);
1 row created.
SQL> commit;
Commit complete.
SQL> select count(*) from usef.tbl1;
COUNT(*)
———-
0
SQL> truncate table usef.tbl1;
Table truncated.
SQL> exec dbms_memoptimize_admin.writes_flush();
PL/SQL procedure successfully completed.
SQL> select count(*) from usef.tbl1;
COUNT(*)
———-
1
2.استفاده از روش Fast Ingest سه خطر احتمالی زیر را در پی دارد:
الف: در صورت crash کردن instance، اطلاعاتی که در Large Pool قرار دارند، از بین خواهند رفت.
ب: اطلاعات درج شده، معمولا بلافاصله قابل رویت نیستند.
ج: با اتفاقاتی چون پر شدن فضای دیتابیس و tablespace و … ممکن است دیتا از دست برود.