3 min read

Part 6. Manual Deployment Strategy: REST Triggers, Admin UI, Parameter Reprocessing, Rollback

We summarize API design, permission control, audit trail, reprocessing, and rollback strategies to safely operate manual deployment.

Series: Spring Boot 배치 전략 완전 정복

12편 구성. 현재 6편을 보고 있습니다.

썸네일 - 운영자 모니터링
썸네일 - 운영자 모니터링

Source: Pexels - Security control room team

Based on version

  • Java 21
  • Spring Boot 3.3.x
  • Spring Batch 5.2.x
  • Quartz 2.3.x
  • PostgreSQL 15
  • OpenSearch 2.x

1) Raise a problem

Automated deployment alone cannot address all operational needs. In actual operation, the following requests are repeated:

  • "Please reprocess only specific customer data."
  • "Please re-run only the section from 02:00 to 03:00 yesterday."
  • "The batch results are wrong and an immediate rollback is required."

In this case, processing with ad-hoc SQL is faster, but reproducibility and audit trails are lost. The goal of manual deployment is safe control rather than convenience.

2) Summary of key concepts

Manual deployment must have at least the following four things:

  1. Separation of execution rights: Separation of view rights and execution rights.
  2. Verification of input parameters: date range, number of target IDs, limit to maximum number of cases.
  3. Execution history/audit log: Save who executed, when, and with what parameters.
  4. Rollback path: Back application possible based on the result table.

Transaction isolation level is especially important in manual reprocessing. Since the target set must not be shaken during operator reprocessing, it is common to use REPEATABLE READ snapshot reads and split the write section into short chunk commits.

Manual batch execution flow

Mermaid diagram rendering...

본문 이미지 - 경고등(리스크 관리)
본문 이미지 - 경고등(리스크 관리)

Source: Pexels - Warning Red Beacon

3) Code example

Example A: Manual execution API

@RestController
@RequestMapping("/admin/batches")
@RequiredArgsConstructor
public class ManualBatchController {

    private final ManualBatchService manualBatchService;

    @PostMapping("/reprocess-orders")
    public ResponseEntity<ManualBatchResponse> reprocess(@RequestBody ReprocessRequest request,
                                                         @AuthenticationPrincipal AdminUser adminUser) {
        ManualBatchResponse response = manualBatchService.startReprocess(request, adminUser.getEmail());
        return ResponseEntity.accepted().body(response);
    }
}

Example B: Parameter validation + execution registration

@Transactional
public ManualBatchResponse startReprocess(ReprocessRequest req, String requestedBy) {
    if (req.startDate().isAfter(req.endDate())) {
        throw new IllegalArgumentException("startDate must be <= endDate");
    }
    if (ChronoUnit.DAYS.between(req.startDate(), req.endDate()) > 7) {
        throw new IllegalArgumentException("max range is 7 days");
    }

    String requestHash = DigestUtils.sha256Hex(req.toString());
    Long executionId = manualBatchExecutionRepository.insertPending("reprocess-orders", requestedBy, requestHash, req);

    jobLauncher.run(reprocessJob, new JobParametersBuilder()
        .addLong("executionId", executionId)
        .addString("requestHash", requestHash)
        .toJobParameters());

    return new ManualBatchResponse(executionId, "PENDING");
}

Example C: Audit log table SQL

CREATE TABLE manual_batch_execution (
    id BIGSERIAL PRIMARY KEY,
    job_name VARCHAR(100) NOT NULL,
    requested_by VARCHAR(120) NOT NULL,
    request_hash VARCHAR(64) NOT NULL,
    request_payload JSONB NOT NULL,
    status VARCHAR(20) NOT NULL,
    created_at TIMESTAMP NOT NULL DEFAULT NOW(),
    started_at TIMESTAMP NULL,
    ended_at TIMESTAMP NULL
);

CREATE UNIQUE INDEX uk_manual_batch_job_hash ON manual_batch_execution (job_name, request_hash);
CREATE INDEX idx_manual_batch_status_created ON manual_batch_execution (status, created_at DESC);

Example D: Keyset query for reprocessing

SELECT id, order_id, error_code
FROM order_sync_failures
WHERE id > :last_id
  AND occurred_at BETWEEN :start_at AND :end_at
ORDER BY id ASC
LIMIT 1000;

4) Real-world failure/operational scenarios

Situation: The operator sent the same reprocessing request three times by refreshing the browser. The system received each request in a separate batch, processed them in duplicate, and duplicate notifications were sent to customers.

Cause:

  • There was no request idempotent key.
  • The UI did not lock the "running" state.
  • Manual batch results were immediately propagated to the external delivery system.

Improvements:

  1. Block duplicate execution with request_hash unique index.
  2. Prohibiting retransmission of same parameters and progress status polling in UI.
  3. Separate external shipments into outbox and add approval step.

5) Design Checklist

  • Is manual batch execution permission separated by RBAC?
  • Is there a limit to the maximum range/number of execution parameters?
  • Is duplicate execution blocked with request idempotent key (request_hash)?
  • Are requesters/parameters/results stored in the audit log?
  • Is the rollback path (reverse SQL or compensating batch) documented?
  • Have you set a chunk/isolation level to reduce DB lock contention during manual execution?

6) Summary

Manual deployment is not an “operational convenience feature” but a control system. Opening an API endpoint alone is not enough; authority, idempotence, auditing, and rollback must be designed together to be safe in actual operation.

7) Next episode preview

In the next part, we will cover DB mass query strategy. Limitations of OFFSET/LIMIT, Keyset Pagination, ID Range Batch, Covering Index, SKIP LOCKED, and snapshot read strategy are compared based on numbers.

Series navigation

Comments