Hello! Ang aking pangalan ay Sergey Kachan, at ako ay isang client developer sa proyekto ng War Robots. War Robots ay nasa loob ng maraming taon, at sa panahon na ito ang laro ay nakumpleto ng isang malaking iba't ibang uri ng materyal: mga robots, armas, drones, titans, mga piloto, atbp. At para sa lahat ng ito sa trabaho, kailangan namin upang i-storage ng isang malaking halaga ng iba't ibang uri ng impormasyon. Ngayon ako ay makipag-usap tungkol sa kung paano ang balances ay na-structured sa aming proyekto, kung ano ang nangyari sa kanila sa loob ng nakaraang 11 taon, at kung paano kami ay nagtatrabaho sa ito. Balances sa Proyekto Tulad ng anumang iba pang proyekto, ang War Robots ay maaaring i-divide sa dalawang bahagi: meta at core gameplay. ang anumang aktibidad na bumaba sa core game loop ngunit pa rin ay nakakaapekto sa gameplay. Ito ay naglalaman ng pagbili at pag-upgrade ng gaming content, pag-participating sa social o event activities. Meta gameplay (metagaming) ang pangunahing repetitive cycle ng mga aksyon na ang manlalaro ay gumagawa sa laro upang makakuha ng kanilang mga target. Sa aming kaso, ito ay mga robot battles sa anumang mga mapa. Core gameplay (core gameplay loop) Ang bawat bahagi ng proyekto ay nangangailangan ng kanyang sarili na balanse, kaya kami din nag-divide balances sa dalawang kategorya - meta at core. Ang mga robot ay tinatawag na Ang mga ito ay kailangan ng separate balances. Skirmish modes A ang Ang mga skirmish mode ay karaniwang event-based, na magagamit para sa mga manlalaro sa panahon ng iba't-ibang mga libreng araw, karaniwang para sa pag-ibig. Halimbawa, ang mga manlalaro ay maaaring mag-atake sa bawat isa sa isang single shot o mag-move around sa zero gravity. Skirmish mode Kaya sa kabuuan, mayroon kaming 4 balances: 2 para sa default mode at 2 para sa Skirmish mode. Sa loob ng 11 taon, ang War Robots ay nakikipag-ugnayan ng isang tonelada ng mahigpit na nilalaman: 95 mga tao 21 Ang mga Titans 175 iba pang mga armas Mga drone 16 mga mamamayan isang malaking bilang ng mga skins, remodels, modules, pilots, towers, ultimate mga bersyon ng content, at mga mapa At tulad ng maaari mong i-imagine, upang gawin ang lahat ng trabaho na ito kailangan nating i-storage ng impormasyon tungkol sa pag-iisip, statistics, availability, mga presyo, at higit pa, higit pa. Sa katunayan, ang aming balanse ay lumaki sa isang indecent size: Default mode Skirmish mode Meta balance 9.2 MB 9.2 MB Core balance 13.1 MB 13.1 MB Meta balance ang 9.2 MB ang 9.2 MB Core balance 13.1 sa loob ng 13.1 sa loob ng Pagkatapos ng ilang mga mabilis na kalkulasyon, natagpuan namin na ang isang player ay kailangan upang i-download Ang mga ito ay maraming! 44.6 MB Hindi namin gusto ang mga manlalaro upang i-download ang mga malaking halaga ng data sa lahat ng pagkakataon ng pagbabago ng balanse. at pag-distribusyon ng maraming data sa pamamagitan ng CDN ay hindi ganap na halaga. Kailangan mong i-remember sa iyo: War Robots ay dumating Noong 2024, ang aming mga mensahe na aktibo ay at ang Mag-log sa lahat ng araw. 300 million registered users 4.7 million people 690 thousand players Ngayon i-imagine ang halaga ng data. Maraming, ano? Kami ay nag-iisip na ito rin. Kaya, kami ay nagpasya upang gawin ang lahat na maaari naming upang mabawasan ang size ng aming balances! Paghahanap ng Problema Ang unang pagkakataon ay upang i-analisa ang mga balanse at subukan upang malaman: "Ano ang nangangailangan ng maraming lugar?" Manually sa pamamagitan ng lahat ay ang pinakahuling bagay na gusto namin upang gawin - ito ay nangangailangan ng mga edad.So, nag-sulat namin ang isang set ng mga tool na kumonekta at kumonekta ang lahat ng impormasyon na kailangan namin tungkol sa mga balances. Ang tool ay magkaroon ng isang balanse file bilang input at, gamit ang pag-reflection, iterate sa pamamagitan ng lahat ng mga estruktura, pagkuha ng data sa mga uri ng impormasyon na ibinigay namin at kung ano ang lugar na nakaupo sa bawat isa. Ang mga resulta ay disappointing: Ang balanse ay % in balance Usage count String 28.478 % 164 553 Int32 27.917 % 161 312 Boolean 6.329 % 36 568 Double 5.845 % 33 772 Int64 4.682 % 27 054 Custom structures 26.749 % — String Ang mga ito ay 28,478% 16553 ang Int32 27 917 % ang 161 312 ang Boolean 6 29% ang 36 568 ang Double 5 845 % ang 3772 ang Int64 4682% ang 27 054 ang Custom structures 26 749 % ang — Basahin ang balanse % in balance Usage count String 34.259 % 232 229 Double 23.370 % 158 418 Int32 20.955 % 142 050 Boolean 5.306 % 34 323 Custom structures 16.11 % — String 44,259 % ang sa pamamagitan ng 229 Double 2370% ang sa pamamagitan ng 158 418 Int32 Pagkakaiba 20 955 % 42550 ang Boolean 5306 % ang 34 323 ang Custom structures 16,1 % ang — Pagkatapos ng pag-analisa ng situasyon, natagpuan namin na At kailangan nating gawin ang isang bagay tungkol dito. strings were taking up far too much space Kaya, natuklasan namin ang isa pang tool. Ang isa na ito ay i-scan ang file ng balanse at lumikha ng isang mapa ng lahat ng mga string kasama ang bilang ng mga beses ang bawat isa ay duplicated. Ang mga resulta ay hindi napakadali. Ang ilang mga strings ay tinatawag na higit sa 10,000 beses! Nakatanggap namin ang problema. Ngayon ang tanong ay: paano natin itago ito? Optimize ang balanse Para sa mga malinaw na mga dahilan, hindi namin maaaring lamang mag-alis ng mga string sa lahat. Strings ay ginagamit para sa mga bagay tulad ng mga localization keys at iba't-ibang mga IDs. Ngunit kung ano ang maaaring gawin ay i-eliminate ang duplication ng string. Ang ideya ay tulad ng simpleng kung saan ito makuha: Lumikha ng isang listahan ng mga unikat na strings para sa bawat balanse (sa pangkalahatan, isang dedicated storage). Ipadala ang listahan na ito kasama ang data. public class BalanceMessage { public BalanceMessageData Data; public StringStorage Storage; public string Version; } Ang StringStorage ay karaniwang isang wrapper sa paligid ng isang listahan ng string. Kapag lumikha namin ang string storage, ang bawat balanse structure ay nag-iingat ang index ng string na ito ay kinakailangan. public class StringStorage { public List<string> Values; public string GetValue(StringIdx id) => Values[id]; } Sa halip ng paglipat ng mga strings mismo sa loob ng balanse structures, nagsimula namin ang paglipat ng index ng kung saan ang string ay ibinigay sa string storage. Para sa: public class SomeBalanceMessage { public string Id; public string Name; public int Amount; } Pagkatapos ng: public class SomeBalanceMessageV2 { public StringIdx Id; public StringIdx Name; public int Amount; } Ang StringIdx ay basahin lamang ng isang wrapper sa paligid ng isang int. Sa paraan na ito, natagpuan namin ang mga direct string transfers sa loob ng mga balanse structures. public readonly struct StringIdx : IEquatable<StringIdx> { private readonly int _id; internal StringIdx(int value) {_id = value; } public static implicit operator int(StringIdx value) => value._id; public bool Equals(StringIdx other) => _id == other._id; } Ang paraan na ito ay i-reduced ang bilang ng mga strings sa mga dekada ng beses. String usage count String usage count Before After Meta balance 164 553 10 082 Core balance 232 229 14 228 Before After Meta balance 16553 ang 10 082 ang Core balance sa pamamagitan ng 229 14 228 ang Hindi ba malaki, hindi ba? Ngunit ito ay lamang ang simula - hindi namin umalis dito. Paglalarawan ng Data Protocol Para sa paglipat at pagproseso ng balanse structures, ginagamit namin ang . MessagePack Ang MessagePack ay isang binary data serialization format na dinisenyo bilang isang mas kompakto at mas mabilis na alternatibo sa JSON. Ito ay dinisenyo para sa efficient na exchange ng data sa pagitan ng mga application o serbisyo, na nagbibigay ng isang malakas na pagbabago sa size ng data - lalo na magagamit kung saan kinakailangan ang pagganap at bandwidth. Ipinanganak, ang MessagePack ay dumating sa isang JSON-like format, kung saan ang data na ginagamit Ito ay tiyak na convenience, ngunit din ay napaka-consuming space.So kami ay nagsisimula na mag-sacrifice ng ilang flexibility at mag-switch sa isang . string keys binary byte array Para sa: public class SomeBalanceMessage { [Key("id")] public string Id; [Key("name")] public string Name; [Key("amount")] public int Amount; } Pagkatapos ng: public class SomeBalanceMessageV2 { [Key(0)] public StringIdx Id; [Key(1)] public StringIdx Name; [Key(2)] public int Amount; } Sinusubukan din namin ang lahat ng mga empty collections — sa halip ng pagpadala ng mga ito, nag-transmit natin ngayon ang mga nilalaman ng null. Ito ay humihinto sa parehong kabuuan ng data at ang oras na kinakailangan para sa serialization at deserialization. Subukan ang mga pagbabago Ang isang golden rule ng mahusay na pag-unlad (at isa na kung saan ay i-save sa iyo ng maraming mga nervous) ay mag-implementate lahat ng mga bagong mga tampok sa isang paraan na maaari mong mabilis na i-roll ang mga ito back if something goes wrong. For that reason, we add all the new features behind "toggles." Sa panahon ng pag-unlad, kailangan naming mag-siguraduhin na ang lahat ng data ay i-transfer correctly. Old at bagong balances - kahit na ang format o struktural - ay nangangailangan upang lumikha ng eksaktong parehong mga halaga. Upang makakuha ng ito, inihayag namin ang isang malaking bilang ng mga test unit para sa bawat balanse. Sa unang pagkakataon, nakipagtulungan namin ang lahat ng mga fields "head-on" - checking ang bawat isa explicitly. Ito ay gumagana, ngunit ito ay gumagana ng oras, at kahit na ang mas maliit na pagbabago sa mga balances ay tumutulong sa mga test, na inihahanda sa amin upang i-rewrite ang mga ito sa pangkalahatan. Sa katunayan, mayroon na kami ng mga ito at natagpuan ang isang mas mahusay na pagsubok na paraan para sa pagitan ng balances. Refleksyon ay dumating sa pagbabago din. Nakatanggap namin ang dalawang bersyon ng mga estruktura ng balanse, halimbawa, SomeBalanceMessage at SomeBalanceMessageV2, at iterated sa pamamagitan ng mga ito — pagitan ng mga bilang ng mga field, mga pangalan, at mga halaga. Kung ang anumang bagay ay hindi matatagpuan, natagpuan namin ang problema. Ang mga resulta ng optimization Sa pamamagitan ng mga pag-optimize na ito, natagpuan kami upang mabawasan ang size ng mga file na inilathala sa pamamagitan ng network at ang oras na nangangailangan upang deserialize ang mga ito sa client. ang file size Old balances Optimized balances Profit Meta balance 9.2 MB 1.28 MB - 86 % Core balance 13.1 MB 2.22 MB - 83 % Meta balance ang 9.2 MB sa pamamagitan ng 1.28 MB 86% ang Core balance 13.1 sa loob ng sa loob ng 22 MB 83% ang Araw ng Deserialization Old balances Optimized balances Profit Meta balance 967 ms 199 ms - 79 % Core balance 1165 ms 265 ms - 77 % Meta balance 967 sa pamamagitan ng 199 sa pamamagitan ng 79% ang Core balance sa pamamagitan ng 1165 ms 265 sa loob ng Mga 77% Data sa Memory Old balances Optimized balances Profit Meta + Core ~ 45.3 MB ~ 33.5 MB - 26 % Meta + Core sa loob ng 45.3 MB sa loob ng 33,5 MB 26% ang mga konklusyon Ang mga resulta ng optimization ay ganap na satisfied kami. Ang mga file ng balance ay na-reduced ng higit sa 80%. Traffic ay bumaba, at ang mga manlalaro ay masaya. Upang sumulat ito: maging cautious sa data na ibinigay mo, at hindi magpadala ng anumang bagay na hindi kailangan. At kung ang iyong mga custom data (prices, statistics, at iba pa) ay naglalaman ng maraming repetition, subukan ang mga ito sa mga unique storages din. Ito ay i-save ka ng maraming megabytes - at maraming pera sa pag-iisip ng mga server ng CDN.