تکرار سطح بالا در Tarantool DBMS

سلام من در حال ایجاد اپلیکیشن برای DBMS هستم ترانتول یک پلتفرم توسعه یافته توسط Mail.ru Group است که یک DBMS با کارایی بالا و یک سرور برنامه به زبان Lua را ترکیب می کند. سرعت بالای راه حل های مبتنی بر Tarantool به ویژه با پشتیبانی از حالت حافظه داخلی DBMS و توانایی اجرای منطق تجاری برنامه در یک فضای آدرس واحد با داده ها به دست می آید. در همان زمان، ماندگاری داده ها با استفاده از تراکنش های ACID تضمین می شود (یک گزارش WAL روی دیسک نگهداری می شود). Tarantool دارای پشتیبانی داخلی برای Replication و Sharding است. از نسخه 2.1، پرس و جوها به زبان SQL پشتیبانی می شوند. Tarantool منبع باز است و تحت مجوز Simplified BSD مجوز دارد. یک نسخه تجاری تجاری نیز وجود دارد.

تکرار سطح بالا در Tarantool DBMS
قدرت را احساس کن! (...از اجرا لذت ببرید)

همه موارد فوق Tarantool را به یک پلتفرم جذاب برای ایجاد برنامه های کاربردی با بار بالا که با پایگاه های داده کار می کنند تبدیل می کند. در چنین برنامه هایی، اغلب نیاز به تکرار داده ها وجود دارد.

همانطور که در بالا ذکر شد، Tarantool دارای تکثیر داده داخلی است. اصل عملکرد آن این است که تمام تراکنش های موجود در گزارش اصلی (WAL) را به صورت متوالی بر روی کپی ها اجرا کند. معمولاً چنین تکراری (ما آن را بیشتر می نامیم سطح پایین) برای اطمینان از تحمل خطای برنامه و/یا برای توزیع بار خواندن بین گره های خوشه استفاده می شود.

تکرار سطح بالا در Tarantool DBMS
برنج. 1. همانندسازی در یک خوشه

نمونه ای از یک سناریوی جایگزین، انتقال داده های ایجاد شده در یک پایگاه داده به پایگاه داده دیگر برای پردازش/نظارت است. در مورد دوم، یک راه حل راحت تر ممکن است استفاده شود سطح بالا تکرار - تکرار داده ها در سطح منطق تجاری برنامه. آن ها ما از یک راه حل آماده ساخته شده در DBMS استفاده نمی کنیم، بلکه به تنهایی در برنامه ای که در حال توسعه هستیم، Replication را پیاده سازی می کنیم. این رویکرد هم مزایا و هم معایبی دارد. بیایید مزایا را فهرست کنیم.

1. صرفه جویی در ترافیک:

  • شما نمی توانید تمام داده ها را منتقل کنید، بلکه فقط بخشی از آن را منتقل کنید (به عنوان مثال، می توانید فقط برخی از جداول، برخی از ستون ها یا رکوردهای آنها را که معیار خاصی را برآورده می کنند) منتقل کنید.
  • بر خلاف تکرار سطح پایین، که به طور مداوم در حالت ناهمزمان (اجرا شده در نسخه فعلی Tarantool - 1.10) یا همزمان (که در نسخه‌های بعدی Tarantool پیاده‌سازی می‌شود) انجام می‌شود، تکرار سطح بالا را می‌توان در جلسات انجام داد (به عنوان مثال، برنامه ابتدا داده ها را همگام می کند - داده های جلسه تبادل، سپس مکثی در تکرار وجود دارد، پس از آن جلسه تبادل بعدی رخ می دهد و غیره).
  • اگر رکوردی چندین بار تغییر کرده باشد، می‌توانید فقط آخرین نسخه آن را انتقال دهید (برخلاف تکرار سطح پایین، که در آن همه تغییرات ایجاد شده در Master به‌طور متوالی بر روی ماکت‌ها پخش می‌شوند).

2. هیچ مشکلی در پیاده سازی تبادل HTTP وجود ندارد، که به شما امکان می دهد پایگاه داده های راه دور را همگام سازی کنید.

تکرار سطح بالا در Tarantool DBMS
برنج. 2. تکرار بر روی HTTP

3. ساختارهای پایگاه داده ای که داده ها بین آنها منتقل می شود لازم نیست یکسان باشند (علاوه بر این، در حالت کلی، حتی می توان از DBMS های مختلف، زبان های برنامه نویسی، پلتفرم ها و غیره استفاده کرد).

تکرار سطح بالا در Tarantool DBMS
برنج. 3. همانند سازی در سیستم های ناهمگن

نقطه ضعف این است که، به طور متوسط، برنامه نویسی دشوارتر / پرهزینه تر از پیکربندی است، و به جای سفارشی کردن عملکرد داخلی، باید عملکرد خود را پیاده سازی کنید.

اگر در موقعیت شما، مزایای فوق بسیار مهم است (یا شرط لازم است)، پس منطقی است که از تکرار سطح بالا استفاده کنید. بیایید به چندین روش برای پیاده سازی تکرار داده های سطح بالا در Tarantool DBMS نگاه کنیم.

به حداقل رساندن ترافیک

بنابراین، یکی از مزایای تکرار در سطح بالا، صرفه جویی در ترافیک است. برای اینکه این مزیت به طور کامل محقق شود، لازم است مقدار داده های منتقل شده در هر جلسه تبادل به حداقل برسد. البته نباید فراموش کرد که در پایان جلسه، گیرنده داده باید با منبع همگام شود (حداقل برای آن قسمت از داده که درگیر تکرار است).

چگونه می توان مقدار داده های منتقل شده را در طول تکرار در سطح بالا به حداقل رساند؟ یک راه حل ساده می تواند انتخاب داده ها بر اساس تاریخ و زمان باشد. برای انجام این کار، می توانید از فیلد تاریخ-زمان از قبل موجود در جدول (در صورت وجود) استفاده کنید. به عنوان مثال، یک سند "سفارش" ممکن است دارای فیلد "زمان اجرای سفارش مورد نیاز" باشد - delivery_time. مشکل این راه حل این است که مقادیر در این فیلد نباید به ترتیبی باشد که با ایجاد سفارشات مطابقت دارد. بنابراین ما نمی توانیم حداکثر مقدار فیلد را به خاطر بسپاریم delivery_time، در جلسه تبادل قبلی ارسال شده است، و در جلسه تبادل بعدی، تمام رکوردهای دارای مقدار فیلد بالاتر را انتخاب کنید delivery_time. رکوردهایی با مقدار فیلد پایین‌تر ممکن است بین جلسات تبادل اضافه شده باشد delivery_time. همچنین نظم می توانست دستخوش تغییراتی شود که با این وجود تاثیری بر این رشته نداشت delivery_time. در هر دو حالت تغییرات از مبدا به مقصد منتقل نمی شود. برای حل این مشکلات، ما نیاز به انتقال داده ها "همپوشانی" داریم. آن ها در هر جلسه تبادل ما تمام داده ها را با مقدار فیلد منتقل می کنیم delivery_time، بیش از یک نقطه در گذشته (به عنوان مثال، N ساعت از لحظه فعلی). با این حال، بدیهی است که برای سیستم‌های بزرگ، این رویکرد بسیار زائد است و می‌تواند صرفه‌جویی در ترافیک را که ما برای آن تلاش می‌کنیم، کاهش دهد. علاوه بر این، جدول در حال انتقال ممکن است فیلدی مرتبط با تاریخ-زمان نداشته باشد.

راه حل دیگر که از نظر پیاده سازی پیچیده تر است، تأیید دریافت داده ها است. در این حالت، در طول هر جلسه تبادل، کلیه داده هایی که دریافت آن توسط گیرنده تایید نشده است، مخابره می شود. برای پیاده سازی این، باید یک ستون بولی به جدول منبع اضافه کنید (به عنوان مثال، is_transferred). اگر گیرنده دریافت رکورد را تأیید کند، فیلد مربوطه مقدار را می گیرد true، پس از آن ورود دیگر درگیر مبادلات نیست. این گزینه پیاده سازی دارای معایب زیر است. ابتدا، برای هر رکورد منتقل شده، باید یک تایید ایجاد و ارسال شود. به طور کلی، این می تواند با دو برابر کردن مقدار داده های منتقل شده و منجر به دو برابر شدن تعداد رفت و برگشت قابل مقایسه باشد. ثانیاً امکان ارسال یک رکورد به چندین گیرنده وجود ندارد (اولین گیرنده ای که دریافت می کند، دریافت را برای خود و سایر گیرندگان تأیید می کند).

روشی که معایب ذکر شده در بالا را ندارد، اضافه کردن یک ستون به جدول ارسالی برای ردیابی تغییرات در ردیف‌های آن است. چنین ستونی می تواند از نوع تاریخ-زمان باشد و باید هر بار که رکوردها اضافه می شوند/تغییر می شوند توسط برنامه تنظیم/به روز شود. به عنوان مثال، اجازه دهید ستون را فراخوانی کنیم update_time. با ذخیره حداکثر مقدار فیلد این ستون برای رکوردهای منتقل شده، می توانیم جلسه تبادل بعدی را با این مقدار شروع کنیم ( رکوردها را با مقدار فیلد انتخاب کنید update_time، بیش از مقدار ذخیره شده قبلی). مشکل رویکرد دوم این است که تغییرات داده ها می تواند به صورت دسته ای رخ دهد. در نتیجه مقادیر فیلد در ستون update_time ممکن است منحصر به فرد نباشد بنابراین، این ستون نمی تواند برای خروجی داده های بخش بندی شده (صفحه به صفحه) استفاده شود. برای نمایش داده‌ها صفحه به صفحه، باید مکانیسم‌های دیگری اختراع کنید که به احتمال زیاد کارایی بسیار پایینی خواهند داشت (به عنوان مثال، بازیابی همه رکوردهای دارای مقدار از پایگاه داده update_time بالاتر از یک معین و تولید تعداد معینی رکورد، با شروع از یک افست معین از ابتدای نمونه).

شما می توانید با کمی بهبود روش قبلی، کارایی انتقال داده را بهبود بخشید. برای انجام این کار، از نوع عدد صحیح (عدد صحیح طولانی) به عنوان مقادیر فیلد ستون برای ردیابی تغییرات استفاده می کنیم. بیایید نام ستون را بگذاریم row_ver. مقدار فیلد این ستون همچنان باید با هر بار ایجاد/تغییر رکورد تنظیم/به روز شود. اما در این حالت به فیلد تاریخ-زمان فعلی اختصاص داده نمی شود، بلکه مقدار شمارنده یک عدد افزایش می یابد. در نتیجه، ستون row_ver حاوی مقادیر منحصربه‌فردی است و می‌تواند نه تنها برای نمایش داده‌های «دلتا» (داده‌های اضافه/تغییر شده از پایان جلسه تبادل قبلی)، بلکه برای تجزیه ساده و مؤثر آن به صفحات نیز استفاده شود.

آخرین روش پیشنهادی برای به حداقل رساندن مقدار داده های منتقل شده در چارچوب تکرار سطح بالا به نظر من بهینه ترین و جهانی است. بیایید با جزئیات بیشتری به آن نگاه کنیم.

انتقال داده با استفاده از شمارشگر نسخه ردیف

پیاده سازی سرور/بخش اصلی

در MS SQL Server، یک نوع ستون خاص برای پیاده سازی این رویکرد وجود دارد - rowversion. هر پایگاه داده یک شمارنده دارد که هر بار که رکوردی در جدولی که دارای ستونی مانند اضافه یا تغییر می شود، یک عدد افزایش می یابد. rowversion. مقدار این شمارنده به طور خودکار به فیلد این ستون در رکورد اضافه/تغییر داده می شود. Tarantool DBMS مکانیزم داخلی مشابهی ندارد. با این حال، در Tarantool پیاده سازی آن به صورت دستی دشوار نیست. بیایید ببینیم چگونه این کار انجام می شود.

ابتدا کمی اصطلاحات: جداول در Tarantool فضاها و رکوردها تاپل نامیده می شوند. در Tarantool می توانید دنباله هایی ایجاد کنید. دنباله ها چیزی بیش از مولدهای نامگذاری شده مقادیر صحیح مرتب شده نیستند. آن ها این دقیقا همان چیزی است که ما برای اهداف خود نیاز داریم. در زیر چنین دنباله ای ایجاد خواهیم کرد.

قبل از انجام هر عملیات پایگاه داده در Tarantool، باید دستور زیر را اجرا کنید:

box.cfg{}

در نتیجه، Tarantool شروع به نوشتن عکس های فوری پایگاه داده و گزارش تراکنش ها در دایرکتوری فعلی می کند.

بیایید یک دنباله ایجاد کنیم row_version:

box.schema.sequence.create('row_version',
    { if_not_exists = true })

گزینه if_not_exists اجازه می دهد تا اسکریپت ایجاد چندین بار اجرا شود: اگر شی وجود داشته باشد، Tarantool سعی نخواهد کرد دوباره آن را ایجاد کند. این گزینه در تمام دستورات بعدی DDL استفاده خواهد شد.

بیایید یک فضا به عنوان مثال ایجاد کنیم.

box.schema.space.create('goods', {
    format = {
        {
            name = 'id',
            type = 'unsigned'

        },
        {
            name = 'name',
            type = 'string'

        },
        {
            name = 'code',
            type = 'unsigned'

        },
        {
            name = 'row_ver',
            type = 'unsigned'

        }
    },
    if_not_exists = true
})

در اینجا نام فضا را تعیین می کنیم (goods)، نام فیلدها و انواع آنها.

فیلدهای افزایش خودکار در Tarantool نیز با استفاده از دنباله ها ایجاد می شوند. بیایید یک کلید اولیه افزایش خودکار بر اساس فیلد ایجاد کنیم id:

box.schema.sequence.create('goods_id',
    { if_not_exists = true })
box.space.goods:create_index('primary', {
    parts = { 'id' },
    sequence = 'goods_id',
    unique = true,
    type = 'HASH',
    if_not_exists = true
})

Tarantool از چندین نوع شاخص پشتیبانی می کند. پرکاربردترین ایندکس ها انواع TREE و HASH هستند که بر اساس ساختارهای مربوط به نام هستند. TREE متنوع ترین نوع شاخص است. این به شما امکان می دهد تا داده ها را به روشی سازمان یافته بازیابی کنید. اما برای انتخاب برابری، HASH مناسب تر است. بر این اساس، توصیه می شود از HASH برای کلید اصلی (که ما انجام دادیم) استفاده کنید.

برای استفاده از ستون row_ver برای انتقال داده های تغییر یافته، باید مقادیر توالی را به فیلدهای این ستون متصل کنید row_ver. اما برخلاف کلید اصلی، مقدار فیلد ستون row_ver نه تنها هنگام افزودن رکوردهای جدید، بلکه هنگام تغییر رکوردهای موجود، باید یک عدد افزایش یابد. برای این کار می توانید از محرک ها استفاده کنید. Tarantool دو نوع محرک فضایی دارد: before_replace и on_replace. هر زمان که داده ها در فضا تغییر می کنند، تریگرها فعال می شوند (برای هر تاپلی که تحت تأثیر تغییرات قرار می گیرد، یک تابع ماشه راه اندازی می شود). بر خلاف on_replace, before_replace-triggers به ​​شما این امکان را می دهد که داده های تاپلی را که تریگر برای آن اجرا می شود، تغییر دهید. بر این اساس، آخرین نوع محرک ها برای ما مناسب است.

box.space.goods:before_replace(function(old, new)
    return box.tuple.new({new[1], new[2], new[3],
        box.sequence.row_version:next()})
end)

ماشه زیر جایگزین مقدار فیلد می شود row_ver تاپل را به مقدار بعدی دنباله ذخیره کرد row_version.

برای اینکه بتوانیم اطلاعات را از فضا استخراج کنیم goods توسط ستون row_ver، بیایید یک شاخص ایجاد کنیم:

box.space.goods:create_index('row_ver', {
    parts = { 'row_ver' },
    unique = true,
    type = 'TREE',
    if_not_exists = true
})

نوع شاخص - درختی (TREE)، زیرا ما باید داده ها را به ترتیب افزایشی مقادیر در ستون استخراج کنیم row_ver.

بیایید مقداری داده به فضا اضافه کنیم:

box.space.goods:insert{nil, 'pen', 123}
box.space.goods:insert{nil, 'pencil', 321}
box.space.goods:insert{nil, 'brush', 100}
box.space.goods:insert{nil, 'watercolour', 456}
box.space.goods:insert{nil, 'album', 101}
box.space.goods:insert{nil, 'notebook', 800}
box.space.goods:insert{nil, 'rubber', 531}
box.space.goods:insert{nil, 'ruler', 135}

زیرا فیلد اول یک شمارنده افزایش خودکار است؛ به جای آن عدد صفر را پاس می کنیم. Tarantool به طور خودکار مقدار بعدی را جایگزین می کند. به طور مشابه، به عنوان مقدار فیلدهای ستون row_ver شما می توانید صفر را ارسال کنید - یا اصلاً مقدار را مشخص نکنید، زیرا این ستون آخرین موقعیت را در فضا اشغال می کند.

بیایید نتیجه درج را بررسی کنیم:

tarantool> box.space.goods:select()
---
- - [1, 'pen', 123, 1]
  - [2, 'pencil', 321, 2]
  - [3, 'brush', 100, 3]
  - [4, 'watercolour', 456, 4]
  - [5, 'album', 101, 5]
  - [6, 'notebook', 800, 6]
  - [7, 'rubber', 531, 7]
  - [8, 'ruler', 135, 8]
...

همانطور که می بینید اولین و آخرین فیلد به صورت خودکار پر می شوند. اکنون نوشتن تابعی برای آپلود صفحه به صفحه تغییرات فضا آسان خواهد بود goods:

local page_size = 5
local function get_goods(row_ver)
    local index = box.space.goods.index.row_ver
    local goods = {}
    local counter = 0
    for _, tuple in index:pairs(row_ver, {
        iterator = 'GT' }) do
        local obj = tuple:tomap({ names_only = true })
        table.insert(goods, obj)
        counter = counter + 1
        if counter >= page_size then
            break
        end
    end
    return goods
end

تابع مقدار را به عنوان پارامتر می گیرد row_ver، که از آن باید تغییرات را تخلیه کرد و بخشی از داده های تغییر یافته را برمی گرداند.

نمونه گیری داده ها در Tarantool از طریق نمایه ها انجام می شود. تابع get_goods از یک تکرار کننده با شاخص استفاده می کند row_ver برای دریافت داده های تغییر یافته نوع Iterator GT (بزرگتر از، بزرگتر از) است. این بدان معنی است که تکرار کننده به صورت متوالی مقادیر شاخص را که از کلید عبور داده شده شروع می شود (مقدار فیلد) عبور می دهد. row_ver).

تکرار کننده تاپل ها را برمی گرداند. برای اینکه متعاقباً بتوان داده ها را از طریق HTTP انتقال داد، لازم است تاپل ها را به ساختاری مناسب برای سریال سازی بعدی تبدیل کنیم. مثال برای این کار از تابع استاندارد استفاده می کند tomap. بجای استفاده از tomap شما می توانید تابع خود را بنویسید. برای مثال، ممکن است بخواهیم نام یک فیلد را تغییر دهیم name، میدان نگذرد code و یک فیلد اضافه کنید comment:

local function unflatten_goods(tuple)
    local obj = {}
    obj.id = tuple.id
    obj.goods_name = tuple.name
    obj.comment = 'some comment'
    obj.row_ver = tuple.row_ver
    return obj
end

اندازه صفحه داده های خروجی (تعداد رکوردها در یک قسمت) توسط متغیر تعیین می شود page_size. در مثال مقدار page_size در یک برنامه واقعی، اندازه صفحه معمولا اهمیت بیشتری دارد. این بستگی به اندازه متوسط ​​تاپل فضایی دارد. اندازه بهینه صفحه را می توان به صورت تجربی با اندازه گیری زمان انتقال داده تعیین کرد. هرچه اندازه صفحه بزرگتر باشد، تعداد دفعات رفت و برگشت بین طرف ارسال کننده و گیرنده کمتر است. به این ترتیب می توانید زمان کلی دانلود تغییرات را کاهش دهید. با این حال، اگر اندازه صفحه خیلی بزرگ باشد، زمان زیادی را صرف سریال سازی نمونه توسط سرور خواهیم کرد. در نتیجه، ممکن است در پردازش سایر درخواست‌هایی که به سرور ارسال می‌شوند، تاخیر وجود داشته باشد. پارامتر page_size را می توان از فایل پیکربندی بارگذاری کرد. برای هر فضای ارسالی، می توانید مقدار خود را تعیین کنید. با این حال، برای بیشتر فضاها مقدار پیش فرض (مثلاً 100) ممکن است مناسب باشد.

بیایید تابع را اجرا کنیم get_goods:

tarantool> get_goods(0)

---
- - row_ver: 1
    code: 123
    name: pen
    id: 1
  - row_ver: 2
    code: 321
    name: pencil
    id: 2
  - row_ver: 3
    code: 100
    name: brush
    id: 3
  - row_ver: 4
    code: 456
    name: watercolour
    id: 4
  - row_ver: 5
    code: 101
    name: album
    id: 5
...

بیایید مقدار فیلد را بگیریم row_ver از خط آخر و دوباره تابع را فراخوانی کنید:

tarantool> get_goods(5)

---
- - row_ver: 6
    code: 800
    name: notebook
    id: 6
  - row_ver: 7
    code: 531
    name: rubber
    id: 7
  - row_ver: 8
    code: 135
    name: ruler
    id: 8
...

یک بار دیگر:

tarantool> get_goods(8)
---
- []
...

همانطور که می بینید، با استفاده از این روش، تابع تمام رکوردهای فاصله را صفحه به صفحه برمی گرداند goods. صفحه آخر با یک انتخاب خالی دنبال می شود.

بیایید تغییراتی در فضا ایجاد کنیم:

box.space.goods:update(4, {{'=', 6, 'copybook'}})
box.space.goods:insert{nil, 'clip', 234}
box.space.goods:insert{nil, 'folder', 432}

مقدار فیلد را تغییر داده ایم name برای یک ورودی و دو ورودی جدید اضافه شد.

بیایید آخرین فراخوانی تابع را تکرار کنیم:

tarantool> get_goods(8)
---



- - row_ver: 9
    code: 800
    name: copybook
    id: 6
  - row_ver: 10
    code: 234
    name: clip
    id: 9
  - row_ver: 11
    code: 432
    name: folder
    id: 10
...

تابع رکوردهای تغییر یافته و اضافه شده را برگرداند. بنابراین تابع get_goods به شما امکان می دهد داده هایی را دریافت کنید که از آخرین تماس آن تغییر کرده است، که اساس روش تکرار مورد بررسی است.

صدور نتایج از طریق HTTP در قالب JSON را خارج از محدوده این مقاله می گذاریم. در این مورد می توانید اینجا بخوانید: https://habr.com/ru/company/mailru/blog/272141/

پیاده سازی بخش مشتری/برده

بیایید ببینیم پیاده سازی طرف گیرنده چگونه به نظر می رسد. بیایید یک فضای در سمت دریافت کننده برای ذخیره داده های دانلود شده ایجاد کنیم:

box.schema.space.create('goods', {
    format = {
        {
            name = 'id',
            type = 'unsigned'

        },
        {
            name = 'name',
            type = 'string'

        },
        {
            name = 'code',
            type = 'unsigned'

        }
    },
    if_not_exists = true
})

box.space.goods:create_index('primary', {
    parts = { 'id' },
    sequence = 'goods_id',
    unique = true,
    type = 'HASH',
    if_not_exists = true
})

ساختار فضا شبیه ساختار فضا در منبع است. اما از آنجایی که قرار نیست داده های دریافتی را در جای دیگری منتقل کنیم، ستون row_ver در فضای گیرنده نیست. در زمینه id شناسه های منبع ثبت خواهد شد. بنابراین، در سمت گیرنده، نیازی به افزایش خودکار آن نیست.

علاوه بر این، ما به فضایی برای ذخیره مقادیر نیاز داریم row_ver:

box.schema.space.create('row_ver', {
    format = {
        {
            name = 'space_name',
            type = 'string'

        },
        {
            name = 'value',
            type = 'string'

        }
    },
    if_not_exists = true
})

box.space.row_ver:create_index('primary', {
    parts = { 'space_name' },
    unique = true,
    type = 'HASH',
    if_not_exists = true
})

برای هر فضای بارگذاری شده (فیلد space_name) آخرین مقدار بارگذاری شده را در اینجا ذخیره می کنیم row_ver (رشته value). ستون به عنوان کلید اصلی عمل می کند space_name.

بیایید یک تابع برای بارگذاری داده های فضایی ایجاد کنیم goods از طریق HTTP برای این کار به کتابخانه ای نیاز داریم که یک کلاینت HTTP را پیاده سازی کند. خط زیر کتابخانه را بارگیری می کند و کلاینت HTTP را نمونه سازی می کند:

local http_client = require('http.client').new()

ما همچنین به یک کتابخانه برای deserialization json نیاز داریم:

local json = require('json')

این برای ایجاد یک تابع بارگذاری داده کافی است:

local function load_data(url, row_ver)
    local url = ('%s?rowVer=%s'):format(url,
        tostring(row_ver))
    local body = nil
    local data = http_client:request('GET', url, body, {
        keepalive_idle =  1,
        keepalive_interval = 1
    })
    return json.decode(data.body)
end

تابع یک درخواست HTTP را به آدرس url اجرا می کند و آن را ارسال می کند row_ver به عنوان یک پارامتر و نتیجه deserialized درخواست را برمی گرداند.

تابع ذخیره داده های دریافتی به صورت زیر است:

local function save_goods(goods)
    local n = #goods
    box.atomic(function()
        for i = 1, n do
            local obj = goods[i]
            box.space.goods:put(
                obj.id, obj.name, obj.code)
        end
    end)
end

چرخه ذخیره داده در فضا goods قرار داده شده در یک تراکنش (از تابع برای این استفاده می شود box.atomic) برای کاهش تعداد عملیات دیسک.

در نهایت، تابع هماهنگ سازی فضای محلی goods با یک منبع می توانید آن را به صورت زیر پیاده سازی کنید:

local function sync_goods()
    local tuple = box.space.row_ver:get('goods')
    local row_ver = tuple and tuple.value or 0

    —— set your url here:
    local url = 'http://127.0.0.1:81/test/goods/list'

    while true do
        local goods = load_goods(url, row_ver)

        local count = #goods
        if count == 0 then
            return
        end

        save_goods(goods)

        row_ver = goods[count].rowVer
        box.space.row_ver:put({'goods', row_ver})
    end
end

ابتدا مقدار ذخیره شده قبلی را می خوانیم row_ver برای فضا goods. اگر گم شده باشد (اولین جلسه تبادل)، آن را به عنوان در نظر می گیریم row_ver صفر در مرحله بعد، ما یک بارگیری صفحه به صفحه از داده های تغییر یافته را از منبع در آدرس مشخص شده انجام می دهیم. در هر تکرار، داده های دریافتی را در فضای محلی مناسب ذخیره می کنیم و مقدار را به روز می کنیم row_ver (در فضای row_ver و در متغیر row_ver) - مقدار را بگیرید row_ver از آخرین خط داده های بارگذاری شده

برای محافظت در برابر حلقه تصادفی (در صورت بروز خطا در برنامه)، حلقه while را می توان جایگزین کرد for:

for _ = 1, max_req do ...

در نتیجه اجرای تابع sync_goods فضا goods گیرنده حاوی آخرین نسخه های تمام رکوردهای فضایی خواهد بود goods در منبع

بدیهی است که حذف داده ها به این شکل قابل پخش نیست. اگر چنین نیازی وجود دارد، می توانید از علامت حذف استفاده کنید. به فضا اضافه کنید goods میدان بولی is_deleted و به جای حذف فیزیکی یک رکورد، از حذف منطقی استفاده می کنیم - مقدار فیلد را تنظیم می کنیم is_deleted به معنا true. گاهی به جای یک فیلد بولی is_deleted استفاده از میدان راحت تر است deleted، که تاریخ-زمان حذف منطقی رکورد را ذخیره می کند. پس از انجام حذف منطقی، رکورد علامت گذاری شده برای حذف از مبدا به مقصد منتقل می شود (طبق منطقی که در بالا توضیح داده شد).

دنباله row_ver می تواند برای انتقال داده ها از فضاهای دیگر استفاده شود: نیازی به ایجاد یک دنباله جداگانه برای هر فضای ارسالی نیست.

ما به روشی موثر برای تکثیر داده های سطح بالا در برنامه های کاربردی با استفاده از Tarantool DBMS نگاه کردیم.

یافته ها

  1. Tarantool DBMS محصولی جذاب و امیدوارکننده برای ایجاد برنامه های کاربردی با بار بالا است.
  2. تکثیر داده های سطح بالا دارای چندین مزیت نسبت به تکرار سطح پایین است.
  3. روش تکرار سطح بالا که در مقاله مورد بحث قرار گرفت به شما این امکان را می دهد که با انتقال تنها آن دسته از رکوردهایی که از آخرین جلسه تبادل تغییر کرده اند، مقدار داده های منتقل شده را به حداقل برسانید.

منبع: www.habr.com

اضافه کردن نظر