Onlayn imtihon javoblari yo‘qolishining oldini oluvchi texnologiya bilan tanishing

Onlayn imtihon javoblari yo‘qolishining oldini oluvchi texnologiya bilan tanishing

Onlayn ta’lim saytini yuritar ekanmiz, o‘quvchilarning o‘zlashtirish natijalarini baholash uchun muntazam ravishda onlayn imtihonlar o‘tkaza boshladik. Tayyorlagan imtihonlarimizda bir vaqtning o‘zida taxminan 100 nafar ishtirokchi qatnashishi kutilgan edi. Asosan oddiy sahifa ko‘rishlardan iborat odatiy veb-trafik bilan solishtirganda, bu unchalik katta ko‘lamdek tuyulmasligi mumkin.

Biroq onlayn imtihonlarning xususiyatlarini chuqurroq o‘rganganimizda, muhim bir xatarni aniqladik: imtihon tugagan ayni paytda barcha o‘quvchilarning javoblarni saqlash va topshirish so‘rovlari qisqa, bir soniyalik oraliqda bir vaqtda kelib tushishi mumkin edi. Agar bunga beqaror shaxsiy tarmoq sharoitlari sababli mijoz tomonidan yuboriladigan takroriy so‘rovlar va frontend taymerlaridan avtomatik topshirish so‘rovlari ham qo‘shilsa, hatto bitta o‘quvchining javoblari ham yo‘qolib qolishi mumkin edi.

Haqiqiy imtihon paytida javoblar yo‘qolsa, bu nafaqat o‘quvchilarning halol mehnatini zoye qiladi, balki butun ta’lim platformasining ishonchliligiga ham jiddiy putur yetkazadi. Shu sababli imtihonni o‘quvchilar uchun ochishdan oldin jamoamiz javoblar yo‘qolishiga olib kelishi mumkin bo‘lgan ssenariylarni belgilab chiqdi va buning oldini olish uchun uchta muhim texnologiyani oldindan joriy etdi. Ushbu maqolada imtihon yakunidagi so‘rovlar oqimi va bir vaqtdalik muammolarini hal qilish uchun qaysi texnologiyalarni tanlaganimiz va joriy qilganimiz, shuningdek, birorta ham javobni yo‘qotmasdan imtihonni qanday ishonchli o‘tkazganimiz haqida so‘z yuritamiz.

1. Dastlabki tekshiruvlarda aniqlangan javoblar yo‘qolishi ssenariylari

Texnologiyalarni joriy etishdan oldin odatiy sinxron ma’lumotlar bazasida saqlash arxitekturasida imtihon tugashiga oz qolganda yuzaga kelishi mumkin bo‘lgan muammolarni ko‘rib chiqdik. Natijada haqiqiy imtihonda ro‘y berishi ehtimoli bor bo‘lgan javoblar yo‘qolishining uchta aniq ssenariysini aniqladik.

1.1 Imtihon yakunidagi so‘rovlar oqimi va paketlarning tartibsiz kelishi

O‘quvchilar odatda imtihon tugashidan oldingi lahzalarda yozma javoblarini takomillashtiradi yoki ko‘p variantli savollardagi tanlovlarini o‘zgartiradi. Agar taxminan 100 nafar o‘quvchi so‘nggi bir necha soniyada javoblarini tahrirlasa va avtomatik saqlash ishga tushsa, ma’lumotlar bazasiga birdaniga juda ko‘p yozish so‘rovlari kelib tushishi mumkin. Wi-Fi yoki mobil ulanish nuqtasi kabi beqaror tarmoqlardan foydalanayotgan o‘quvchilarda paketlar tartibsiz kelishi, ya’ni so‘nggi tahrirlarni o‘z ichiga olgan keyingi so‘rov serverga undan oldin yuborilgan so‘rovdan avval yetib borishi mumkin. Oddiy, ustidan yozishga asoslangan arxitekturada kechikib kelgan eski so‘rov eng so‘nggi javoblarning ustidan yozib, yangilanishlarning yo‘qolishiga olib kelishi mumkin.

1.2 Qo‘lda topshirish va taymer orqali avtomatik topshirish o‘rtasidagi poyga holati

Bunga misol sifatida o‘quvchi imtihon tugashidan bir soniya oldin “Topshirish” tugmasini bosgan ayni paytda brauzer taymeri muddati tugab, avtomatik topshirish API’sini chaqirishini keltirish mumkin. Tarmoq kechikishi sababli mijoz tomonidan qayta urinishlar ham yuz bersa, topshirish va saqlash bo‘yicha bir nechta tranzaksiya bir imtihon urinishining ma’lumotlariga bir necha millisoniya farq bilan bir vaqtda murojaat qilishi mumkin. Holat qat’iy boshqarilmasa, kechikib kelgan saqlash so‘rovi avtomatik topshirish yakunlangan zahoti topshirilgan javoblarni o‘zgartirishi yoki takroriy topshirish xatosi yuz berib, imtihonni odatdagidek yakunlashga xalaqit berishi mumkin.

1.3 Mijoz qurilmasi soatining og‘ishi va muddatni tekshirish

Frontend taymeri o‘quvchining kompyuteridagi tizim soatiga tayanadi. Agar o‘quvchi qurilmasining soati servernikidan orqada qolsa yoki muddat tugashidan sal oldin yuborilgan paket tarmoq kechikishi sababli serverga bir soniya kech yetib borsa, server hisobiga ko‘ra so‘rov muddat tugagandan keyin kelgan bo‘ladi. Server uni shunchaki rad etsa, o‘quvchi o‘z vaqtida topshirgan bo‘lsa ham, so‘nggi javoblarini yo‘qotishi mumkin.

2. Javoblar yo‘qolishining oldini olish uchun joriy etilgan asosiy texnologiyalar va ularni tanlash sabablari

Yuqorida aniqlangan muammolarning oldini olish uchun biz uchta texnologiyani joriy etdik: trafikni o‘ziga qabul qilish va versiyalarni boshqarish uchun Kafka asosidagi avtomatik saqlash quvuri; bir vaqtdalik to‘qnashuvlarini oldini olish uchun topshirish holatlari mashinasi va qayta urinib foydalanish mumkin bo‘lgan topshirish API’si; topshirishlarning qachon qabul qilinganini adolatli aniqlash uchun esa serverga asoslangan muddat tekshiruvi.

2.1 Kafka asosidagi asinxron avtomatik saqlash va versiyaga asoslangan yangilanishlar

  • Joriy etilgan texnologiya: Apache Kafka hodisalar oqimi va mijoz ketma-ketlik raqamlaridan foydalanadigan shartli ma’lumotlar bazasi yangilanishlari

  • Tanlashimiz sababi: Imtihon davomida yaratiladigan ko‘plab avtomatik saqlash so‘rovlari va yakunidan oldingi yozish trafigining keskin ko‘payishini ma’lumotlar bazasi sinxron tarzda qayta ishlasa, ulanishlarning kechikishi va blokirovkalar bilan bog‘liq muammolar muqarrar bo‘ladi. Shu sababli, saqlash so‘rovlari oqimini ularning birortasini yo‘qotmasdan tez qabul qilish va ma’lumotlar bazasi ko‘tara oladigan sur’atda xavfsiz qo‘llash uchun Kafka’ni joriy etdik.

Kafka orqali asinxron ishlov berilganda yoki tarmoq paketlari tartibsiz kelganda ham eski ma’lumotlarning yangilari ustidan yozib yuborishiga yo‘l qo‘ymaslik uchun versiyaga asoslangan yangilanishlarni ham joriy qildik. Javoblarni yuborishda mijoz monoton ravishda ortib boradigan ketma-ketlik raqamini (clientSeq) ham yuboradi. Har bir o‘quvchiga tegishli hodisalar tartibini ta’minlash uchun Kafka bo‘limi kalitini urinish seansi identifikatoriga (attemptId) o‘rnatdik. Bundan tashqari, ma’lumotlar bazasini yangilashda faqat ketma-ketlik raqami hozir saqlanayotgan raqamdan kattaroq bo‘lgan so‘rovlar qo‘llanishini ta’minlaydigan shartli yangilash so‘rovlarini sozladik.

package com.edu.exam.producer;

import org.springframework.kafka.core.KafkaTemplate;
import org.springframework.stereotype.Service;
import java.time.Instant;

@Service
public class AnswerAutoSaveProducer {

    private static final String TOPIC_NAME = "exam-answer-autosave-events";
    private final KafkaTemplate<String, AnswerSaveEvent> kafkaTemplate;

    public AnswerAutoSaveProducer(KafkaTemplate<String, AnswerSaveEvent> kafkaTemplate) {
        this.kafkaTemplate = kafkaTemplate;
    }

    /**
     * Publishes auto-save requests to Kafka partitioned by attemptId
     * to guarantee ordering per examinee and absorb traffic spikes.
     */
    public void publishAutoSaveEvent(long attemptId, long questionId, String content, long clientSeq) {
        AnswerSaveEvent event = new AnswerSaveEvent(
                attemptId,
                questionId,
                content,
                clientSeq,
                Instant.now().toEpochMilli()
        );

        // Using attemptId as the partition key ensures sequential processing for the same user
        kafkaTemplate.send(TOPIC_NAME, String.valueOf(attemptId), event);
    }

    public record AnswerSaveEvent(
            long attemptId,
            long questionId,
            String answerContent,
            long clientSeq,
            long serverReceivedAtMs
    ) {}
}

Kafka iste’molchilari mavzudagi hodisalarni o‘qib, ma’lumotlar bazasiga qo‘llaydi. Paketlar tarmoq darajasida allaqachon tartibsiz kelgan bo‘lsa ham, quyidagi shartli so‘rov ularni xato chiqarmasdan xavfsiz ravishda e’tiborsiz qoldiradi.

package com.edu.exam.consumer;

import com.edu.exam.producer.AnswerAutoSaveProducer.AnswerSaveEvent;
import org.springframework.jdbc.core.namedparam.MapSqlParameterSource;
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate;
import org.springframework.kafka.annotation.KafkaListener;
import org.springframework.stereotype.Component;

import java.sql.Timestamp;
import java.time.Instant;

@Component
public class AnswerAutoSaveConsumer {

    private final NamedParameterJdbcTemplate jdbcTemplate;

    public AnswerAutoSaveConsumer(NamedParameterJdbcTemplate jdbcTemplate) {
        this.jdbcTemplate = jdbcTemplate;
    }

    /**
     * Consumes auto-save events and performs a version-checked upsert.
     * Prevents out-of-order network packets from overwriting newer answers.
     */
    @KafkaListener(topics = "exam-answer-autosave-events", groupId = "exam-autosave-group")
    public void consumeAndSaveAnswer(AnswerSaveEvent event) {
        String sql = """
            INSERT INTO exam_answer
                (attempt_id, question_id, answer_text, last_client_seq, updated_at)
            VALUES
                (:attemptId, :questionId, :answerText, :clientSeq, :updatedAt)
            ON DUPLICATE KEY UPDATE
                answer_text     = IF(:clientSeq > last_client_seq, :answerText, answer_text),
                updated_at      = IF(:clientSeq > last_client_seq, :updatedAt, updated_at),
                last_client_seq = IF(:clientSeq > last_client_seq, :clientSeq, last_client_seq)
            """;

        MapSqlParameterSource params = new MapSqlParameterSource()
                .addValue("attemptId", event.attemptId())
                .addValue("questionId", event.questionId())
                .addValue("answerText", event.answerContent())
                .addValue("clientSeq", event.clientSeq())
                .addValue("updatedAt", Timestamp.from(Instant.ofEpochMilli(event.serverReceivedAtMs())));

        jdbcTemplate.update(sql, params);
    }
}

Ushbu arxitektura yordamida, imtihon tugashiga sal qolganda saqlash so‘rovlari birdaniga ko‘payib ketsa ham, API server hodisalarni tezda Kafka’ga yuborib, javob qaytara oladi va shu tariqa to‘siq yuzaga kelishining oldini oladi. Bundan tashqari, :clientSeq > last_client_seq sharti ma’lumotlar bazasi darajasida tekshirilgani uchun paketlar tartibsiz kelsa ham o‘quvchi yozgan eng so‘nggi javoblar to‘g‘ri saqlanib qoladi.

2.2 Serverga asoslangan muddat tekshiruvi (server vaqti yagona mezon va qo‘shimcha vaqt)

  • Joriy etilgan texnologiya: Server vaqtini yagona mezon sifatida olish va tarmoq kechikishini hisobga oladigan qo‘shimcha vaqt bilan tekshirish

  • Tanlashimiz sababi: Mijoz qurilmasining tizim soati noto‘g‘ri bo‘lishi yoki istalgan paytda o‘zgartirilishi mumkinligi sababli, muddat tugagan-tugamaganini aniqlashda server mezon bo‘lishi kerak. Biroq faqat server vaqtiga qat’iy tayanish imtihon tugashidan sal oldin odatdagidek yuborilgan so‘rovni tarmoq orqali uzatish vaqti tufayli juda oz farq bilan rad etishga olib kelishi mumkin. Shu sababli, har bir so‘rovning kelib tushgan vaqtini server soati bo‘yicha qayd etamiz, ammo tarmoq kechikishi sababli javoblar yo‘qolishining oldini olish uchun rasmiy tugash vaqtidan keyin bir necha soniyalik qo‘shimcha vaqt beramiz— bu bizning yechimimizda ko‘zda tutilgan.

Imtihon boshlanganda serverning joriy vaqtini mijozga ham yuboramiz va ekrandagi taymerni server bilan bir xil vaqt mezoniga muvofiq ishlaydigan qilib sozlaymiz.

package com.edu.exam.service;

import org.springframework.stereotype.Component;
import java.time.Duration;
import java.time.Instant;

@Component
public class ExamTimeValidator {

    // Allow a 3-second grace period to absorb network latency at the exact moment of exam termination
    private static final Duration NETWORK_GRACE_PERIOD = Duration.ofSeconds(3);

    /**
     * Validates whether the incoming request arrived within the authoritative server deadline
     * including the allowed network grace period.
     */
    public boolean isWithinValidSubmissionWindow(Instant officialEndTime, Instant serverArrivedAt) {
        Instant validCutoffTime = officialEndTime.plus(NETWORK_GRACE_PERIOD);
        return !serverArrivedAt.isAfter(validCutoffTime);
    }
}

Ushbu tekshiruvchi imtihon tugashiga qadar so‘nggi lahzagacha yozilgan javoblar tarmoq orqali serverga yetib borishi uchun zarur bo‘lgan vaqtni xavfsiz hisobga olib, asosli javoblarning rad etilishiga yo‘l qo‘ymaydi.

2.3 Topshirish holatlari mashinasi va qayta urinish mumkin bo‘lgan idempotent topshirish API’si

  • Joriy etilgan texnologiya: Bir yo‘nalishli topshirish holatlari mashinasi (State Machine) va holatni barqaror yakuniy holatga keltiruvchi idempotent (Idempotency) topshirish API’si

  • Tanlashimiz sababi: Imtihon tugayotgan paytda o‘quvchining qo‘lda topshirish tugmasini bosishi, taymerning avtomatik topshirish so‘rovi va tarmoq kutish vaqti tugagani sababli mijozning qayta urinishlari bir vaqtda yuz berishi mumkin. Server takroriy so‘rov kelgani uchungina xato qaytarsa, o‘quvchi topshirish muvaffaqiyatsiz bo‘lgani haqidagi xabarni ko‘rib, ancha sarosimaga tushadi. Shuningdek, “Topshirish” tugmasi bosilishidan oldingi so‘nggi 1–2 soniyada yozilgan javoblar hali avtomatik saqlanmagan bo‘lsa, yo‘qolishi mumkin.

Buni hal qilish uchun imtihon holatini faqat IN_PROGRESS -> SUBMITTING -> SUBMITTED (yoki AUTO_SUBMITTED) yo‘nalishida o‘tishi mumkin bo‘lgan bir yo‘nalishli holatlar mashinasi sifatida belgiladik va ikkita himoya mexanizmini joriy etdik.

  1. Holatni atomar o‘zgartirish (CAS) va idempotent javoblar: Shartli ma’lumotlar bazasi yangilanishidan (WHERE status = 'IN_PROGRESS') foydalanib, bir vaqtda kelgan qo‘lda topshirish va avtomatik topshirish so‘rovlaridan faqat bittasiga holatni o‘zgartirishga ruxsat berilishini ta’minlaymiz. Keyin kelgan takroriy yoki qayta yuborilgan so‘rovlar imtihon allaqachon yopilganini (SUBMITTED yoki AUTO_SUBMITTED) tekshiradi va xato o‘rniga ayni muvaffaqiyatli topshirish javobini qaytaradi. Shu tariqa, istalgan vaqtda xavfsiz qayta urinib foydalanish mumkin bo‘lgan API’ga ega bo‘lamiz.

  2. Yakuniy javoblarni topshirish so‘rovi bilan birga yuborish (Piggybacking): Topshirish API’sini so‘rov tanasiga javoblarning eng so‘nggi o‘zgarishlarini ham kiritadigan qilib yaratdik. Shu tufayli, Kafka iste’molchilari kechikishidan qat’i nazar, yakuniy javoblarni topshirish tranzaksiyasi ichida darhol qo‘llash va imtihon holatini yopish mumkin.

package com.edu.exam.service;

import com.edu.exam.consumer.AnswerAutoSaveConsumer;
import com.edu.exam.producer.AnswerAutoSaveProducer.AnswerSaveEvent;
import org.springframework.jdbc.core.namedparam.MapSqlParameterSource;
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.sql.Timestamp;
import java.time.Instant;
import java.util.List;

@Service
public class ExamSubmissionService {

    private final NamedParameterJdbcTemplate jdbcTemplate;
    private final ExamTimeValidator timeValidator;
    private final AnswerAutoSaveConsumer answerSaver;

    public ExamSubmissionService(
            NamedParameterJdbcTemplate jdbcTemplate,
            ExamTimeValidator timeValidator,
            AnswerAutoSaveConsumer answerSaver) {
        this.jdbcTemplate = jdbcTemplate;
        this.timeValidator = timeValidator;
        this.answerSaver = answerSaver;
    }

    /**
     * Processes final exam submission using a unidirectional state machine.
     * Guarantees idempotency when manual submit, auto submit, and client retries overlap.
     */
    @Transactional
    public SubmissionResult submitExam(long attemptId, boolean isAutoSubmit, List<AnswerSaveEvent> finalAnswers) {
        Instant serverNow = Instant.now();

        ExamAttemptRecord attempt = findAttemptById(attemptId);

        // 1. Idempotency check: If already finalized by a prior request, return success immediately
        if ("SUBMITTED".equals(attempt.status()) || "AUTO_SUBMITTED".equals(attempt.status())) {
            return new SubmissionResult(attemptId, attempt.status(), "ALREADY_COMPLETED_SUCCESS");
        }

        // 2. Atomic State Machine Transition: IN_PROGRESS -> SUBMITTING
        String casSql = """
            UPDATE exam_attempt
            SET status = 'SUBMITTING', updated_at = :now
            WHERE attempt_id = :attemptId AND status = 'IN_PROGRESS'
            """;

        int updatedRows = jdbcTemplate.update(casSql, new MapSqlParameterSource()
                .addValue("attemptId", attemptId)
                .addValue("now", Timestamp.from(serverNow)));
        if (updatedRows == 0) {
            // Another concurrent request is already processing or has just finished submission
            ExamAttemptRecord refreshed = findAttemptById(attemptId);
            if ("SUBMITTED".equals(refreshed.status()) || "AUTO_SUBMITTED".equals(refreshed.status())) {
                return new SubmissionResult(attemptId, refreshed.status(), "CONVERGED_SUCCESS");
            }
            throw new IllegalStateException("Submission is currently in progress. Please retry.");
        }

        // 3. Validate server deadline and flush final piggybacked answers using version check
        if (timeValidator.isWithinValidSubmissionWindow(attempt.officialEndTime(), serverNow) && finalAnswers != null) {
            for (AnswerSaveEvent answer : finalAnswers) {
                answerSaver.consumeAndSaveAnswer(new AnswerSaveEvent(
                        attemptId,
                        answer.questionId(),
                        answer.answerContent(),
                        answer.clientSeq(),
                        serverNow.toEpochMilli()
                ));
            }
        }

        // 4. Finalize state transition: SUBMITTING -> SUBMITTED or AUTO_SUBMITTED
        String targetStatus = isAutoSubmit ? "AUTO_SUBMITTED" : "SUBMITTED";
        String finalizeSql = """
            UPDATE exam_attempt
            SET status = :targetStatus, submitted_at = :now, updated_at = :now
            WHERE attempt_id = :attemptId AND status = 'SUBMITTING'
            """;

        jdbcTemplate.update(finalizeSql, new MapSqlParameterSource()
                .addValue("attemptId", attemptId)
                .addValue("targetStatus", targetStatus)
                .addValue("now", Timestamp.from(serverNow)));

        return new SubmissionResult(attemptId, targetStatus, "NEWLY_SUBMITTED_SUCCESS");
    }

    private ExamAttemptRecord findAttemptById(long attemptId) {
        String sql = "SELECT attempt_id, status, official_end_time FROM exam_attempt WHERE attempt_id = :id";
        return jdbcTemplate.queryForObject(sql, new MapSqlParameterSource("id", attemptId), (rs, rowNum) ->
                new ExamAttemptRecord(
                        rs.getLong("attempt_id"),
                        rs.getString("status"),
                        rs.getTimestamp("official_end_time").toInstant()
                )
        );
    }

    public record ExamAttemptRecord(long attemptId, String status, Instant officialEndTime) {}
    public record SubmissionResult(long attemptId, String finalStatus, String resultCode) {}
}

Ushbu yechimda topshirish yakunlanganidan keyin ham Kafka navbatida kechikib qolgan avtomatik saqlash hodisalari bo‘lsa va iste’molchi ularni keyinroq qayta ishlasa ham, topshirish vaqtida eng katta ketma-ketlik raqamiga ega yakuniy javob allaqachon ma’lumotlar bazasiga qo‘llangan bo‘ladi va ma’lumotlar xavfsiz saqlanadi. Shuningdek, masalan, kompyuteri o‘chib qolishi sababli topshirish so‘rovini umuman yubora olmaydigan o‘quvchilar uchun ham himoya mexanizmini qo‘shdik: server rejalashtiruvchisi muddati o‘tgan IN_PROGRESS seanslarni vaqti-vaqti bilan topib, xuddi shu holatlar mashinasi mantig‘idan foydalanib ularni xavfsiz tarzda avtomatik yopadi.

3. Natijalar va xulosa

Haqiqiy imtihon o‘tkazishdan oldin yuqorida ta’riflangan uchta muhim texnologiyani tizimga tatbiq etdik, ularni sinovdan o‘tkazdik va so‘ng taxminan 100 nafar o‘quvchi uchun onlayn imtihon tashkil qildik.

Imtihon davomida va u tugagan ayni paytda o‘quvchilarning javoblarni tahrirlash va topshirish so‘rovlari bir vaqtda kelib tushdi. Kafka asosidagi asinxron avtomatik saqlash quvuri tufayli server va ma’lumotlar bazasi barcha so‘rovlarni hech qanday to‘siq yoki kechikishsiz ishonchli tarzda qayta ishladi. Tarmog‘i beqaror bo‘lgan ayrim o‘quvchilarda paketlar tartibsiz keldi, biroq ketma-ketlik raqamlariga asoslangan shartli yangilanishlar eski ma’lumotlarni to‘g‘ri filtrlab, eng so‘nggi yozilgan javoblarni o‘zgarishsiz saqlab qoldi.

Ayniqsa, bir yo‘nalishli topshirish holatlari mashinasi va idempotent API o‘quvchilar qo‘lda topshirish tugmasini bosgan paytda taymer orqali avtomatik topshirish ishga tushganda yoki vaqtinchalik tarmoq uzilishi qayta urinishlarga sabab bo‘lganda ham kutilganidek ishladi. Takroriy so‘rovlar tufayli yuzaga keladigan xatolar ko‘rsatilmasdan, har bir o‘quvchining topshirish jarayoni muammosiz yakunlandi. Serverga asoslangan muddat tekshiruvi va qo‘shimcha vaqt mexanizmi ham imtihon tugashidan sal oldin yuborilgan yakuniy javoblarning barchasi baholashga kiritilishini ta’minladi va birorta ham javob yo‘qolmadi.

Bu o‘n minglab foydalanuvchi bir vaqtda qatnashgan ulkan imtihon bo‘lmagan bo‘lsa-da, onlayn imtihonlarga xos texnik muammууд—trafikning yakun paytida to‘planishi va bir nechta so‘rovning o‘zaro raqobatlashishi—ko‘lamdan qat’i nazar bir xil bo‘ladi. Muammolar yuz berganidan keyin chora ko‘rish o‘rniga, javoblar yo‘qolishi mumkin bo‘lgan ssenariylarni oldindan aniqlab, Kafka asosidagi versiyalar nazorat qilinadigan avtomatik saqlashni, server vaqtiga asoslangan tekshiruvni va holatlar mashinasiga tayangan, qayta urinish mumkin bo‘lgan topshirish API’sini joriy etdik. Bu o‘quvchilarga ishonchli va barqaror imtihon muhitini taqdim etish imkonini berdi.

dwmoon

Site footer