Բարձր մակարդակի կրկնօրինակում Tarantool DBMS-ում

Բարև Ձեզ, ես ստեղծում եմ հավելվածներ DBMS-ի համար Տարանտուլ Mail.ru Group-ի կողմից մշակված հարթակ է, որը միավորում է բարձր արդյունավետությամբ DBMS-ը և Lua լեզվով հավելվածի սերվերը: Tarantool-ի վրա հիմնված լուծումների բարձր արագությունը ձեռք է բերվել, մասնավորապես, շնորհիվ DBMS-ի հիշողության ռեժիմի աջակցության և կիրառական բիզնես տրամաբանությունը տվյալների հետ մեկ հասցեի տարածքում գործարկելու հնարավորության շնորհիվ: Միևնույն ժամանակ տվյալների պահպանումն ապահովվում է ACID գործարքների միջոցով (սկավառակի վրա պահպանվում է WAL մատյան): Tarantool-ն ունի ներկառուցված աջակցություն կրկնօրինակման և բաշխման համար: Սկսած 2.1 տարբերակից՝ աջակցվում են հարցումները SQL լեզվով: Tarantool-ը բաց կոդով է և լիցենզավորված է պարզեցված BSD լիցենզիայի ներքո: Առկա է նաև կոմերցիոն Enterprise տարբերակը։

Բարձր մակարդակի կրկնօրինակում Tarantool DBMS-ում
Զգացեք ուժը: (…նա վայելեք կատարումը)

Վերոնշյալ բոլորը Tarantool-ը դարձնում են գրավիչ հարթակ տվյալների բազաների հետ աշխատող բարձր բեռնված հավելվածներ ստեղծելու համար: Նման հավելվածներում հաճախակի է լինում տվյալների կրկնօրինակման անհրաժեշտություն:

Ինչպես նշվեց վերևում, Tarantool-ն ունի ներկառուցված տվյալների կրկնօրինակում: Նրա գործողության սկզբունքն է կրկնօրինակների վրա հաջորդաբար կատարել հիմնական մատյանում (WAL) պարունակվող բոլոր գործարքները: Սովորաբար նման կրկնօրինակում (մենք այն հետագայում կանվանենք ցածր մակարդակի) օգտագործվում է կիրառման սխալների հանդուրժողականությունն ապահովելու և/կամ կլաստերի հանգույցների միջև ընթերցման բեռը բաշխելու համար:

Բարձր մակարդակի կրկնօրինակում Tarantool DBMS-ում
Բրինձ. 1. Կրկնօրինակում կլաստերի ներսում

Այլընտրանքային սցենարի օրինակ կարող է լինել մեկ տվյալների բազայում ստեղծված տվյալների փոխանցումը մեկ այլ տվյալների բազա՝ մշակման/մոնիթորինգի համար: Վերջին դեպքում ավելի հարմար լուծում կարող է լինել օգտագործելը բարձր մակարդակ replication - տվյալների կրկնօրինակում կիրառական բիզնես տրամաբանության մակարդակում: Նրանք. Մենք չենք օգտագործում DBMS-ում ներկառուցված պատրաստի լուծում, այլ ինքնուրույն իրականացնում ենք կրկնօրինակում մեր մշակվող հավելվածում: Այս մոտեցումն ունի և՛ առավելություններ, և՛ թերություններ: Թվարկենք առավելությունները.

1. Երթևեկության խնայողություն.

  • Դուք չեք կարող փոխանցել բոլոր տվյալները, այլ միայն դրանց մի մասը (օրինակ, կարող եք փոխանցել միայն որոշ աղյուսակներ, դրանց որոշ սյունակներ կամ գրառումներ, որոնք համապատասխանում են որոշակի չափանիշի);
  • Ի տարբերություն ցածր մակարդակի կրկնօրինակման, որը շարունակաբար իրականացվում է ասինխրոն (իրականացված Tarantool-ի ընթացիկ տարբերակում - 1.10) կամ համաժամանակյա (կիրականացվի Tarantool-ի հետագա տարբերակներում), բարձր մակարդակի կրկնօրինակումը կարող է իրականացվել նիստերում (այսինքն՝ հավելվածը նախ համաժամացնում է տվյալները՝ փոխանակման սեսիայի տվյալները, այնուհետև տեղի է ունենում կրկնօրինակման դադար, որից հետո տեղի է ունենում փոխանակման հաջորդ նիստը և այլն);
  • եթե գրառումը մի քանի անգամ փոխվել է, կարող եք փոխանցել միայն դրա վերջին տարբերակը (ի տարբերություն ցածր մակարդակի կրկնօրինակման, որում հիմնականի վրա կատարված բոլոր փոփոխությունները հաջորդաբար կհնչեն կրկնօրինակների վրա):

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-ում աղյուսակները կոչվում են բացատներ, իսկ գրառումները՝ tuples: 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 տեսակները, որոնք հիմնված են անվանմանը համապատասխան կառուցվածքների վրա։ ԾԱՌԸ ցուցիչի ամենաբազմակողմանի տեսակն է: Այն թույլ է տալիս հավաքել տվյալները կազմակերպված կերպով: Բայց հավասարության ընտրության համար ավելի հարմար է 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 պահվում է tuple հաջորդականության հաջորդ արժեքին 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-ի միջոցով, անհրաժեշտ է վերափոխել tuples-ը հետագա սերիականացման համար հարմար կառուցվածքի: Օրինակը դրա համար օգտագործում է ստանդարտ ֆունկցիա 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 5 է: Իրական ծրագրում էջի չափը սովորաբար ավելի մեծ նշանակություն ունի: Դա կախված է տիեզերական tuple-ի միջին չափից: Էջի օպտիմալ չափը կարելի է որոշել էմպիրիկ կերպով՝ չափելով տվյալների փոխանցման ժամանակը: Որքան մեծ է էջի չափը, այնքան փոքր է շրջագայությունների քանակը ուղարկող և ստացող կողմերի միջև: Այս կերպ Դուք կարող եք կրճատել փոփոխությունները ներբեռնելու ընդհանուր ժամանակը: Այնուամենայնիվ, եթե էջի չափը չափազանց մեծ է, մենք չափազանց երկար կծախսենք սերվերի վրա՝ սերիականացնելու նմուշը: Որպես հետեւանք, կարող են լինել ուշացումներ սպասարկող այլ հարցումների մշակման հարցում: Պարամետր 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()

Մեզ նաև գրադարան է պետք 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 որպես պարամետր և վերադարձնում է հարցումի ապասերիալացված արդյունքը:

Ստացված տվյալների պահպանման գործառույթն ունի հետևյալ տեսքը.

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 զրո: Հաջորդ ցիկլում մենք կատարում ենք փոփոխված տվյալների էջ առ էջ ներբեռնում աղբյուրից նշված url-ով: Յուրաքանչյուր կրկնության ժամանակ մենք պահպանում ենք ստացված տվյալները համապատասխան լոկալ տարածությունում և թարմացնում արժեքը 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. Հոդվածում քննարկված վերարտադրման բարձր մակարդակի մեթոդը թույլ է տալիս նվազագույնի հասցնել փոխանցված տվյալների քանակը՝ փոխանցելով միայն այն գրառումները, որոնք փոխվել են վերջին փոխանակման նիստից հետո:

Source: www.habr.com

Գնեք հուսալի հոստինգ DDoS պաշտպանությամբ կայքերի, VPS VDS սերվերների համար 🔥 Գնեք հուսալի կայքերի հոսթինգ՝ DDoS պաշտպանությամբ, VPS VDS սերվերներով | ProHoster