sql - როგორ გავაუმჯობესოთ თანმიმდევრული ჩანართი mysql-ზე
მე ვცდილობ შევქმნა HTTP მოვლენების სტრიმინგის სერვერი MySQL-ის გამოყენებით, სადაც მომხმარებლები შეძლებენ მოვლენების დამატებას სტრიმში (MySQL ცხრილში) და ასევე განსაზღვრონ მოვლენისთვის სასურველი თანმიმდევრობის ნომერი.
ლოგიკა გარკვეულწილად მარტივია:
- გახსნილი ტრანზაქცია
- მიიღეთ სხრეულის შემდეგი სერიული ნომერი ჩასართავად
- შეამოწმეთ, შეესაბამება თუ არა შემდეგი სერიის ნომერი საჭიროებს (თუ მოცემულია)
- ჩასმა ბაზაში
ეს არის ჩემი კოდი:
public async append(
data: any = {},
expectedSeq?: number
): Promise<void> {
let published_at = $date.create();
try {
await $mysql.transaction(async trx => {
let max = await trx(this.table)
.max({
seq: "seq",
})
.first();
if (!max) {
throw $error.InternalError(`unexpected mysql response`);
}
let next = (max.seq || 0) + 1;
if (expectedSeq && expectedSeq !== next) {
throw $error.ExpectationFailed(
`expected seq does not match current seq`
);
}
await trx(this.table).insert({
published_at,
seq: next,
data: $json.stringify(data),
});
});
} catch (err) {
if (err.code === "ER_DUP_ENTRY") {
return this.append(data, expectedSeq);
}
throw err;
}
}
ჩემს პრობლემას წარმოადგენს ის, რომ ეს ძალიან ნელია, რადგან ერთიდაიგივე ნაკადზე პარალელური მოთხოვნებს შორის ხდება რასის კონდიციები.. ჩემი ლეპტოპი ერთ ნაკადზე ჩანაწერებს წამში უკვე ~1k-დან ~75-მდე ითვლის.
არის რაიმე რჩევები/განცხადებები იმის შესახებ, როგორ შეიძლება ოპტიმიზაცია გაუკეთოთ ამ ლოგიკას?
დასკვნა
კომენტარების მიმოხილვის შემდეგ, გადავწყვიტე ავტომატური ზრდის გამოყენება, და ავტომატური ზრდის წარდგენა უნდა მოხდეს მხოლოდ მაშინ, თუ არსებობს შეცდომა. ეს ქმნის დაახლოებით ისეთივე ტრანზაქციებს/პატაკებს როგორც expectedSeq, მაგრამ გაცილებით უფრო სწრაფად, თუ რიგის შენარჩუნება არ არის საჭირო.
გთავაზობთ გადაწყვეტილებას:
public async append(data: any = {}, expectedSeq?: number): Promise<Event> {
if (!$validator.validate(data, this.schema)) {
throw $error.ValidationFailed("validation failed for event data");
}
let published_at = $date.create();
try {
let seq = await $mysql.transaction(async _trx => {
let result = (await _trx(this.table).insert({
published_at,
data: $json.stringify(data),
})).shift();
if (!result) {
throw $error.InternalError(`unexpected mysql response`);
}
if (expectedSeq && expectedSeq !== result) {
throw $error.ExpectationFailed(
`expected seq ${expectedSeq} but got ${result}`
);
}
return result;
});
return eventFactory(this.topic, seq, published_at, data);
} catch (err) {
await $mysql.raw(`ALTER TABLE ${this.table} auto_increment = ${this.seqStart}`);
throw err;
}
}
პასუხი #1
რატომ სჭირდება ვებგვერდს სერიული ნომრის მიწოდება? ეს უბრალოდ არეული კოდის რეცეპტია, შესაძლოა კიდევ უფრო არეული ვიდრე ის, რასაც დახატეთ. უბრალოდ დატოვეთ auto_increment-ის მნიშვნელობა მომხმარებლისთვის დაბრუნდეს.
INSERT ...;SELECT LAST_INSERT_ID();-- სესიაზე სპეციფიკური, ამიტომ ტრანსაქციის საჭირო არ არის.- გაბრუნე ეს მნიშვნელობა მომხმარებელს.
პასუხი #2
რატომ არ გამოიყენოთ Apache Kafka, ეს ყველაფერს ნატურალურად აკეთებს. როდესაც მარტივი პასუხი უკვე გამოჩნდა, ოპტიმიზაცია ყოველთვის რთულია ნაწილობრივი ინფორმაციის პირობებში, თუმცა ვფიქრობ, რომ თქვენ შეგვთავაზეთ ერთ-ერთი მინიშნება, რომელიც შეიძლება საშუალებას მოგცეთ შემოთავაზების გაკეთება. თქვენ თქვით, რომ without the order clause ეს უფრო სწრაფად მუშაობს, რაც ნიშნავს, რომ მაქსიმალური მნიშვნელობის მიღება უთხრა ხელს, რამდენად გრძელია პროცესში. ეს რამდენიმე რამეს მიგანიშნებს: პირველ რიგში, ეს მნიშვნელობა არაა clustered index (რაც კარგი ამბავია), მეორეც, სავარაუდოდ, თქვენ არ გაქვთ საკმარისი ინდექსის მხარდაჭერა (ესეც კარგი ამბავია, რადგან შეგიძლიათ დაარეგულიროთ, შექმნათ ინდექსი ამ სვეტზე და დაალაგოთ ინდექსი დაწვერილი რიგით). ეს ჟღერს, როგორც მილიონობით ან მილიარდობით ხაზიანი ცხრილი, და ეს კონკრეტული სვეტი არ აქვს გარანტირებული მოსაწყობი თანმიმდევრობა, სწორად ინდექსირების გარეშე შეგიძლიათ გააკეთოთ ცხრილის სკანირება ჩასმების დროს მაქსიმალური მნიშვნელობის მისაღებად.