30 KiB
3.11 Document Numbering Management & Implementation (V1.8.0)
title: 'Specifications & Implementation Guide: Document Numbering System' version: 1.8.0 status: draft owner: Nattanin Peancharoen / Development Team last_updated: 2026-02-23 related:
- specs/01-requirements/01-01-objectives.md
- specs/02-architecture/README.md
- specs/03-implementation/03-02-backend-guidelines.md
- specs/04-operations/04-08-document-numbering-operations.md
- specs/07-database/07-01-data-dictionary-v1.8.0.md
- specs/05-decisions/ADR-002-document-numbering-strategy.md Clean Version v1.8.0 – Scope of Changes:
- รวม Functional Requirements เข้ากับ Implementation Guide
- เลือกใช้ Single Numbering System (Option A)
document_number_countersเป็น Authoritative Counter - เพิ่ม Idempotency Key, Reservation (Two-Phase Commit)
- Number State Machine, Pattern Validate UTF-8, Cancellation Rule (Void/Replace) references:
- Document Numbering
- Document Numbering
📖 เอกสารฉบับนี้เป็นการรวมรายละเอียดจาก
01-03.11-document-numbering.mdและ03-04-document-numbering.mdให้อยู่ในฉบับเดียวสำหรับใช้อ้างอิงการออกแบบเชิง Functional และการพัฒนา Technology Component
1. Overview & วัตถุประสงค์ (Purpose)
ระบบ Document Numbering สำหรับสร้างเลขที่เอกสารอัตโนมัติที่มีความเป็นเอกลักษณ์ (unique) และสามารถติดตามได้ (traceable) สำหรับเอกสารทุกประเภทในระบบ LCBP3-DMS
1.1 Requirements Summary & Scope
- Auto-generation: สร้างเลขที่อัตโนมัติ ไม่ซ้ำ (Unique) ยืดหยุ่น
- Configurable Templates: รองรับแบบฟอร์มกำหนดค่า สำหรับโปรเจกต์ ประเภทเอกสาร ฯลฯ
- Uniqueness Guarantee: การันตี Uniqueness ใน Concurrent Environment (Race Conditions)
- Manual override: รองรับการ Import หรือ Override สำหรับเอกสารเก่า
- Cancelled/Void Handling: ไม่นำหมายเลขที่ใช้ หรือ Cancel/Void กลับมาใช้ใหม่ (No reuse)
- Audit Logging: บันทึกเหตุการณ์ Operation ทั้งหมดอย่างละเอียดครบถ้วน 7 ปี
1.2 Technology Stack
| Component | Technology |
|---|---|
| Backend Framework | NestJS 10.x |
| ORM | TypeORM 0.3.x |
| Database | MariaDB 11.8 |
| Cache/Lock | Redis 7.x + Redlock |
| Message Queue | BullMQ |
| Monitoring | Prometheus + Grafana |
1.3 Architectural Decision (AD-DN-001)
ระบบเลือกใช้ Option A:
document_number_countersเป็น Core / Authoritative Counter System.document_numbering_configs(หรือdocument_number_formats) ใช้เฉพาะกำหนดระเบียบเลข (Template format) และ Permission.- เหตุผล: ลดความซ้ำซ้อน, ป้องกัน Counter Mismatch, Debug ง่าย, Ops เคลียร์.
2. Business Rules & Logic
2.1 Counter Logic & Reset Policy
การนับเลขจะแยกตาม Counter Key ที่ประกอบด้วยหลายส่วน ซึ่งขึ้นกับประเภทเอกสาร
(project_id, originator_organization_id, recipient_organization_id, correspondence_type_id, sub_type_id, rfa_type_id, discipline_id, reset_scope)
| Document Type | Reset Policy | Counter Key Format / Details |
|---|---|---|
| Correspondence (LETTER, MEMO, RFI) | Yearly reset | (project_id, originator, recipient, type_id, 0, 0, 0, 'YEAR_2025') |
| Transmittal | Yearly reset | (project_id, originator, recipient, type_id, sub_type_id, 0, 0, 'YEAR_2025') |
| RFA | No reset | (project_id, originator, 0, type_id, 0, rfa_type_id, discipline_id, 'NONE') |
| Drawing | Separate Namespace | DRAWING::<project>::<contract> (ไม่ได้ใช้ counter rules เดียวกัน) |
2.2 Format Templates & Supported Tokens
Supported Token Types:
{PROJECT}: รหัสโครงการ (เช่นLCBP3){ORIGINATOR}: รหัสองค์กรส่ง (เช่นคคง.){RECIPIENT}: รหัสองค์กรรับหลัก (เช่นสคฉ.3) *ไม่ใช้กับ RFA{CORR_TYPE}: รหัสประเภทเอกสาร (เช่นRFA,LETTER){SUB_TYPE}: ประเภทย่อย (สำหรับ Transmittal){RFA_TYPE}: รหัสประเภท RFA (เช่นSDW,RPT){DISCIPLINE}: รหัสสาขาวิชา (เช่นSTR,CV){SEQ:n}: Running Number ตามจำนวนหลักnลบด้วยศูนย์นำหน้า{YEAR:B.E.},{YEAR:A.D.},{YYYY},{YY},{MM}: สัญลักษณ์บอกเวลาและปฏิทิน.{REV}: Revision Code (เช่นA,B)
Token Validation Grammar
TEMPLATE := TOKEN ("-" TOKEN)*
TOKEN := SIMPLE | PARAM
SIMPLE := "{PROJECT}" | "{ORIGINATOR}" | "{RECIPIENT}" | "{CORR_TYPE}" | "{DISCIPLINE}" | "{RFA_TYPE}" | "{REV}" | "{YYYY}" | "{YY}" | "{MM}"
PARAM := "{SEQ:" DIGIT+ "}"
DIGIT := "0" | "1" | "2" | "3" | "4" | "5" | "6" | "7" | "8" | "9"
2.3 Character & Format Rules (BR-DN-002, BR-DN-003)
- Document number must be printable UTF‑8 (Thai, English, Numbers,
-,_,.). ไม่อนุญาต Control characters, newlines. - ต้องยาวระหว่าง 10 ถึง 50 ตัวอักษร
- ต้องกำหนด Token
{SEQ:n}ลำดับที่ exactly once. ห้ามเป็น Unknown token ใดๆ.
2.4 Number State Machine & Idempotency
- States Lifecycle:
RESERVED(TTL 5 mins) →CONFIRMED→VOID/CANCELLED. Document ที่ Confirmed แล้วสามารถมีพฤติกรรม VOID ในอนาคตเพื่อแทนที่ด้วยเอกสารใหม่ได้ การ Request จะได้ Document ชุดใหม่ทดแทนต่อเนื่องทันที. ห้าม Reuse เลขเดิมโดยสิ้นเชิง. - Idempotency Key Support: ทุก API ในการ Generator จำเป็นต้องระบุ HTTP Header
Idempotency-Keyป้องกันระบบสร้างเลขเบิ้ล (Double Submission). ถ้าระบบได้รับคู่ Request + Key ชุดเดิม จะ Response เลขที่เอกสารเดิม.
3. Functional Requirements
- FR-DN-001 (Sequential Auto-generation): ระบบตอบกลับความรวดเร็วที่ระดับ < 100ms โดยที่ทน Concurrent ได้ ทนต่อปัญหา Duplicate
- FR-DN-002 (Configurable): สามารถเปลี่ยนรูปแบบเทมเพลตผ่านระบบ Admin ได้ด้วยการ Validation ก่อน حفظ
- FR-DN-003 (Scope-based sequences): รองรับ Scope แยกระดับเอกสาร
- FR-DN-004 (Manual Override): ระบบรองรับการตั้งเลขด้วยตนเองสำหรับ Admin Level พร้อมระบุเหตุผลผ่าน Audit Trails เป็นหลักฐานสำคัญ (Import Legacy, Correction)
- FR-DN-005 (Bulk Import): รับเข้าระบบจากไฟล์ Excel/CSV และแก้ไข Counters Sequence ต่อเนื่องพร้อมเช็ค Duplicates.
- FR-DN-006 (Skip Cancelled): ไม่ให้สิทธิ์ดึงเอกสารยกเลิกใช้งานซ้ำ. คงรักษาสภาพ Audit ต่อไป.
- FR-DN-007 (Void & Replace): เปลี่ยน Status เลขเป็น VOID ไม่มีการ Delete. Reference Link เอกสารใหม่ที่เกิดขึ้นทดแทนอิงตาม
voided_from_id. - FR-DN-008 (Race Condition Prevention): จัดการ Race Condition (RedLock + Database Pessimistic Locking) เพื่อ Guarantee zero duplicate numbers ที่ Load 1000 req/s.
- FR-DN-009 (Two-phase Commit): แบ่งการออกเลขเป็นช่วง Reserve 5 นาที แล้วค่อยเรียก Confirm หลังจากได้เอกสารที่ Submit เรียบร้อย (ลดอาการเลขหาย/เลขฟันหลอที่ยังไม่ถูกใช้).
- FR-DN-010/011 (Audit / Metrics Alerting): Audit ทุกๆ Step / Transaction ไว้ใน DB ให้เสิร์ชได้เกิน 7 ปี. ส่งแจ้งเตือนถ้า Sequence เริ่มเต็ม (เกิน 90%) หรือ Rate error เริ่มสูง.
4. Module System & Code Architecture
4.1 Folder Structure
backend/src/modules/document-numbering/
├── document-numbering.module.ts
├── controllers/ # document-numbering.controller.ts, admin.controller.ts, metrics.controller.ts
├── services/ # Main orchestration (document-numbering.service.ts), lock, counter, reserve, format, audit
├── entities/ # DB Entities mappings
├── dto/ # DTOs
├── validators/ # template.validator.ts
├── guards/ # manual-override.guard.ts
└── jobs/ # counter-reset.job.ts (Cron)
4.2 Sequence Process Architecture
1. Number Generation Process Diagram
sequenceDiagram
participant C as Client
participant S as NumberingService
participant L as LockService
participant CS as CounterService
participant DB as Database
participant R as Redis
C->>S: generateDocumentNumber(dto)
S->>L: acquireLock(counterKey)
L->>R: REDLOCK acquire
R-->>L: lock acquired
L-->>S: lock handle
S->>CS: incrementCounter(counterKey)
CS->>DB: BEGIN TRANSACTION
CS->>DB: SELECT FOR UPDATE
CS->>DB: UPDATE last_number
CS->>DB: COMMIT
DB-->>CS: newNumber
CS-->>S: sequence
S->>S: formatNumber(template, seq)
S->>L: releaseLock()
L->>R: REDLOCK release
S-->>C: documentNumber
2. Two-Phase Commit Pattern (Reserve / Confirm)
sequenceDiagram
participant C as Client
participant RS as ReservationService
participant SS as SequenceService
participant R as Redis
Note over C,R: Phase 1: Reserve
C->>RS: reserve(documentType)
RS->>SS: getNextSequence()
SS-->>RS: documentNumber
RS->>R: SETEX reservation:{token} (TTL: 5min)
RS-->>C: {token, documentNumber, expiresAt}
Note over C,R: Phase 2: Confirm
C->>RS: confirm(token)
RS->>R: GET reservation:{token}
R-->>RS: reservationData
RS->>R: DEL reservation:{token}
RS-->>C: documentNumber (confirmed)
4.3 Lock Service (Redis Redlock Example)
@Injectable()
export class DocumentNumberingLockService {
constructor(@InjectRedis() private readonly redis: Redis) {
this.redlock = new Redlock([redis], { driftFactor: 0.01, retryCount: 5, retryDelay: 100, retryJitter: 50 });
}
async acquireLock(counterKey: CounterKey): Promise<Redlock.Lock> {
const lockKey = this.buildLockKey(counterKey);
return await this.redlock.acquire([lockKey], /* ttl */ 5000); // 5 sec retention
}
}
4.4 Counter Service & Transaction Strategy (Optimistic Example)
async incrementCounter(counterKey: CounterKey): Promise<number> {
const MAX_RETRIES = 2;
for (let attempt = 0; attempt < MAX_RETRIES; attempt++) {
try {
return await this.dataSource.transaction(async (manager) => {
const counter = await manager.findOne(DocumentNumberCounter, { /* rules */ });
if (!counter) {
// Insert base 1
return 1;
}
counter.lastNumber += 1;
await manager.save(counter); // Trigger Optimistic Version Check
return counter.lastNumber;
});
} catch (error) {
// Loop if version mismatch
}
}
}
5. Database Schema Details
5.1 Format Storage & Counters
-- Format Template Configuration
CREATE TABLE document_number_formats (
id INT AUTO_INCREMENT PRIMARY KEY,
project_id INT NOT NULL,
correspondence_type_id INT NULL,
format_template VARCHAR(100) NOT NULL,
reset_sequence_yearly TINYINT(1) DEFAULT 1,
UNIQUE KEY idx_unique_project_type (project_id, correspondence_type_id),
FOREIGN KEY (project_id) REFERENCES projects(id) ON DELETE CASCADE
);
-- Active Sequences
CREATE TABLE document_number_counters (
project_id INT NOT NULL,
correspondence_type_id INT NULL,
originator_organization_id INT NOT NULL,
recipient_organization_id INT NOT NULL DEFAULT 0,
sub_type_id INT DEFAULT 0,
rfa_type_id INT DEFAULT 0,
discipline_id INT DEFAULT 0,
reset_scope VARCHAR(20) NOT NULL,
last_number INT DEFAULT 0 NOT NULL,
version INT DEFAULT 0 NOT NULL,
PRIMARY KEY (... 8 fields combination ...),
INDEX idx_counter_lookup (project_id, correspondence_type_id, reset_scope)
);
5.2 Two-Phase Commit Reservations
CREATE TABLE document_number_reservations (
id BIGINT AUTO_INCREMENT PRIMARY KEY,
token VARCHAR(36) NOT NULL UNIQUE COMMENT 'UUID v4',
document_number VARCHAR(100) NOT NULL UNIQUE,
status ENUM('RESERVED', 'CONFIRMED', 'CANCELLED', 'VOID') NOT NULL DEFAULT 'RESERVED',
document_id INT NULL COMMENT 'Link after confirmed',
expires_at DATETIME(6) NOT NULL,
... Context fields ...
);
5.3 Audit Trails & Error Logs
CREATE TABLE document_number_audit (
id BIGINT AUTO_INCREMENT PRIMARY KEY,
document_number VARCHAR(100) NOT NULL,
operation ENUM('RESERVE', 'CONFIRM', 'CANCEL', 'MANUAL_OVERRIDE', 'VOID', 'GENERATE') NOT NULL,
counter_key JSON NOT NULL,
is_success BOOLEAN DEFAULT TRUE,
lock_wait_ms INT,
... Extraneous Auditing fields ...
);
CREATE TABLE document_number_errors (
id BIGINT AUTO_INCREMENT PRIMARY KEY,
error_type ENUM('LOCK_TIMEOUT','VERSION_CONFLICT','DB_ERROR','REDIS_ERROR','VALIDATION_ERROR','SEQUENCE_EXHAUSTED') NOT NULL,
stack_trace TEXT,
context_data JSON
);
6. Endpoints & API Definitions
| Endpoint | Method | Permission | Meaning |
|---|---|---|---|
/document-numbering/preview |
POST | correspondence.read |
Preview Formats |
/document-numbering/reserve |
POST | correspondence.create |
Reserve Token & Logic Number (2PC) |
/document-numbering/confirm |
POST | correspondence.create |
Confirm Reservation (2PC) |
/document-numbering/cancel |
POST | correspondence.create |
Manual or System Cancel Reservation |
/admin/document-numbering/manual-override |
POST | system.manage_settings |
Inject / Legacy specific number generation |
/admin/document-numbering/void-and-replace |
POST | system.manage_settings |
Replace document marking old logic as VOID |
/admin/document-numbering/bulk-import |
POST | system.manage_settings |
Batch Migration Numbers from legacy |
/admin/document-numbering/templates |
GET/POST | system.manage_settings |
Setting Pattern Configurations |
7. Security, Error Handling & Concurrency Checklists
Fallback Strategy for Database Lock Failures:
- System attempt to acquire
Redlock. - Redis Down? → Fallback DB-only Lock Transaction Layer.
- Redis Online but Timeout
>3 Times? → Return HTTP 503 (Exponential Backoff). - Save Failed via TypeORM Version Confilct? → Retry Loop
2 Times, otherwise Return 409 Conflict.
Rate Limiting Profiles:
- Single User Threshold:
10 requests/minute. - Specific Request IP:
50 requests/minute.
Authorization Policies:
Super Adminเท่านั้นที่บังคับสั่งReset Counterให้เริ่มนับศูนย์ได้เมื่อฉุกเฉิน.- กฎ Audit Log System ระบุชัดเจนว่าต้อง Retain Information ไม่ต่ำกว่า 7 ปี.
8. Monitoring / Observability (Prometheus + Grafana)
| Condition Event | Prometheus Counter Target | Severity Reaction |
|---|---|---|
Utilization >95% |
numbering_sequence_utilization |
🔴 CRITICAL (PagerDuty/Slack). Limit Maximum sequence reached. |
Redis Downtime >1M |
Health System | 🔴 CRITICAL (PagerDuty/Slack) |
Lock Latency p95 >1s |
numbering_lock_wait_seconds |
🟡 WARNING (Slack). Redis connection struggling. |
| Error Rate Burst | numbering_lock_failures_total |
🟡 WARNING (Slack). Need investigation logs check |
9. Testing & Rollout Migration Strategies
9.1 Test Approach Requirements
- Unit Tests: Template Tokens Validations, Error handling retry, Optimistic locking checks.
- Concurrency Integration Test: Assert >1000 requests without double generating numbers simultaneously per
project_id. - E2E Load Sequence Flow: Mocking bulk API loads over 500 requests per seconds via CI/CD load pipeline.
9.2 Data Rollout Plan (Legacy Legacy Import)
- Dump out existing Sequence numbers (Extracted Document Strings).
- Write validation script for Sequence Max Counts.
- Import to Table
document_number_countersas Manual Override API Method context (FR-DN-004). - Re-Verify next sequence logic output
+1count seamlessly integrates tonextNumber().
Best Practices Checklist
- ✅ DO: Two-Phase Commit (
Reserve+Confirm) ให้เป็น Routine System Concept. - ✅ DO: DB Fallback เมื่อ Redis ดาวน์. ให้ Availability สูงสุด ไม่หยุดทำงาน.
- ✅ DO: ข้ามเลขที่ยกเลิกทั้งหมดห้ามมีการ Re-Use เด็ดขาด ไม่ว่าเจตนาใดๆ.
- ❌ DON'T: ไม่แก้ Sequence จาก DB Console ตรงๆ โดยเด็ดขาด.
- ❌ DON'T: ลืม Validate format หรือ Tokens แปลกๆ ในระบบ Template (ต้อง Check Grammar ตลอดไป).
- ❌ DON'T: ลืมเขียน Idempotency-Key สำหรับ Request.
Document Version: 1.8.0 Created By: Development Team End of Document
10. Operations & Infrastructure Guidelines
1. Performance Requirements
1.1. Response Time Targets
| Metric | Target | Measurement |
|---|---|---|
| 95th percentile | ≤ 2 วินาที | ตั้งแต่ request ถึง response |
| 99th percentile | ≤ 5 วินาที | ตั้งแต่ request ถึง response |
| Normal operation | ≤ 500ms | ไม่มี retry |
1.2. Throughput Targets
| Load Level | Target | Notes |
|---|---|---|
| Normal load | ≥ 50 req/s | ใช้งานปกติ |
| Peak load | ≥ 100 req/s | ช่วงเร่งงาน |
| Burst capacity | ≥ 200 req/s | Short duration (< 1 min) |
1.3. Availability SLA
- Uptime: ≥ 99.5% (excluding planned maintenance)
- Maximum downtime: ≤ 3.6 ชั่วโมง/เดือน (~ 8.6 นาที/วัน)
- Recovery Time Objective (RTO): ≤ 30 นาที
- Recovery Point Objective (RPO): ≤ 5 นาที
2. Infrastructure Setup
2.1. Database Configuration
MariaDB Connection Pool
// ormconfig.ts
{
type: 'mysql',
host: process.env.DB_HOST,
port: parseInt(process.env.DB_PORT),
username: process.env.DB_USERNAME,
password: process.env.DB_PASSWORD,
database: process.env.DB_DATABASE,
extra: {
connectionLimit: 20, // Pool size
queueLimit: 0, // Unlimited queue
acquireTimeout: 10000, // 10s timeout
retryAttempts: 3,
retryDelay: 1000
}
}
High Availability Setup
# docker-compose.yml
services:
mariadb-master:
image: mariadb:11.8
environment:
MYSQL_REPLICATION_MODE: master
MYSQL_ROOT_PASSWORD: ${DB_ROOT_PASSWORD}
volumes:
- mariadb-master-data:/var/lib/mysql
networks:
- backend
mariadb-replica:
image: mariadb:11.8
environment:
MYSQL_REPLICATION_MODE: slave
MYSQL_MASTER_HOST: mariadb-master
MYSQL_MASTER_ROOT_PASSWORD: ${DB_ROOT_PASSWORD}
volumes:
- mariadb-replica-data:/var/lib/mysql
networks:
- backend
2.2. Redis Configuration
Redis Sentinel for High Availability
# docker-compose.yml
services:
redis-master:
image: redis:7-alpine
command: redis-server --appendonly yes
volumes:
- redis-master-data:/data
networks:
- backend
redis-replica:
image: redis:7-alpine
command: redis-server --replicaof redis-master 6379 --appendonly yes
volumes:
- redis-replica-data:/data
networks:
- backend
redis-sentinel:
image: redis:7-alpine
command: >
redis-sentinel /etc/redis/sentinel.conf
--sentinel monitor mymaster redis-master 6379 2
--sentinel down-after-milliseconds mymaster 5000
--sentinel failover-timeout mymaster 10000
networks:
- backend
Redis Connection Pool
// redis.config.ts
import IORedis from 'ioredis';
export const redisConfig = {
host: process.env.REDIS_HOST || 'localhost',
port: parseInt(process.env.REDIS_PORT) || 6379,
password: process.env.REDIS_PASSWORD,
maxRetriesPerRequest: 3,
enableReadyCheck: true,
lazyConnect: false,
poolSize: 10,
retryStrategy: (times: number) => {
if (times > 3) {
return null; // Stop retry
}
return Math.min(times * 100, 3000);
},
};
2.3. Load Balancing
Nginx Configuration
# nginx.conf
upstream backend {
least_conn; # Least connections algorithm
server backend-1:3000 max_fails=3 fail_timeout=30s weight=1;
server backend-2:3000 max_fails=3 fail_timeout=30s weight=1;
server backend-3:3000 max_fails=3 fail_timeout=30s weight=1;
keepalive 32;
}
server {
listen 80;
server_name api.lcbp3.local;
location /api/v1/document-numbering/ {
proxy_pass http://backend;
proxy_http_version 1.1;
proxy_set_header Connection "";
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_next_upstream error timeout;
proxy_connect_timeout 10s;
proxy_send_timeout 30s;
proxy_read_timeout 30s;
}
}
Docker Compose Scaling
# docker-compose.yml
services:
backend:
image: lcbp3-backend:latest
deploy:
replicas: 3
resources:
limits:
cpus: '1.0'
memory: 1G
reservations:
cpus: '0.5'
memory: 512M
restart_policy:
condition: on-failure
delay: 5s
max_attempts: 3
environment:
NODE_ENV: production
DB_POOL_SIZE: 20
networks:
- backend
4. Troubleshooting Runbooks
4.1. Scenario: Redis Unavailable
Symptoms:
- Alert:
RedisUnavailable - System falls back to DB-only locking
- Performance degraded 30-50%
Action Steps:
-
Check Redis status:
docker exec lcbp3-redis redis-cli ping # Expected: PONG -
Check Redis logs:
docker logs lcbp3-redis --tail=100 -
Restart Redis (if needed):
docker restart lcbp3-redis -
Verify failover (if using Sentinel):
docker exec lcbp3-redis-sentinel redis-cli -p 26379 SENTINEL masters -
Monitor recovery:
- Check metric:
docnum_redis_connection_statusreturns to 1 - Check performance: P95 latency returns to normal (< 500ms)
- Check metric:
4.2. Scenario: High Lock Failure Rate
Symptoms:
- Alert:
HighLockFailureRate(> 10%) - Users report "ระบบกำลังยุ่ง" errors
Action Steps:
-
Check concurrent load:
# Check current request rate curl http://prometheus:9090/api/v1/query?query=rate(docnum_generation_duration_ms_count[1m]) -
Check database connections:
SHOW PROCESSLIST; -- Look for waiting/locked queries -
Check Redis memory:
docker exec lcbp3-redis redis-cli INFO memory -
Scale up if needed:
# Increase backend replicas docker-compose up -d --scale backend=5 -
Check for deadlocks:
SHOW ENGINE INNODB STATUS; -- Look for LATEST DETECTED DEADLOCK section
4.3. Scenario: Slow Performance
Symptoms:
- Alert:
SlowDocumentNumberGeneration - P95 > 2 seconds
Action Steps:
-
Check database query performance:
SELECT * FROM document_number_counters USE INDEX (idx_counter_lookup) WHERE project_id = 2 AND correspondence_type_id = 6 AND current_year = 2025; -- Check execution plan EXPLAIN SELECT ...; -
Check for missing indexes:
SHOW INDEX FROM document_number_counters; -
Check Redis latency:
docker exec lcbp3-redis redis-cli --latency -
Check network latency:
ping mariadb-master ping redis-master -
Review slow query log:
docker exec lcbp3-mariadb-master cat /var/log/mysql/slow.log
4.4. Scenario: Version Conflicts
Symptoms:
- High retry count
- Users report "เลขที่เอกสารถูกเปลี่ยน" errors
Action Steps:
-
Check concurrent requests to same counter:
SELECT project_id, correspondence_type_id, COUNT(*) as concurrent_requests FROM document_number_audit WHERE created_at > NOW() - INTERVAL 5 MINUTE GROUP BY project_id, correspondence_type_id HAVING COUNT(*) > 10 ORDER BY concurrent_requests DESC; -
Investigate specific counter:
SELECT * FROM document_number_counters WHERE project_id = X AND correspondence_type_id = Y; -- Check audit trail SELECT * FROM document_number_audit WHERE counter_key LIKE '%project_id:X%' ORDER BY created_at DESC LIMIT 20; -
Check for application bugs:
- Review error logs for stack traces
- Check if retry logic is working correctly
-
Temporary mitigation:
- Increase retry count in application config
- Consider manual counter adjustment (last resort)
5. Maintenance Procedures
5.1. Counter Reset (Manual)
Requires: SUPER_ADMIN role + 2-person approval
Steps:
-
Request approval via API:
POST /api/v1/document-numbering/configs/{configId}/reset-counter { "reason": "เหตุผลที่ชัดเจน อย่างน้อย 20 ตัวอักษร", "approver_1": "user_id", "approver_2": "user_id" } -
Verify in audit log:
SELECT * FROM document_number_config_history WHERE config_id = X ORDER BY changed_at DESC LIMIT 1;
5.2. Template Update
Best Practices:
- Always test template in staging first
- Preview generated numbers before applying
- Document reason for change
- Template changes do NOT affect existing documents
API Call:
PUT /api/v1/document-numbering/configs/{configId}
{
"template": "{ORIGINATOR}-{RECIPIENT}-{SEQ:4}-{YEAR:B.E.}",
"change_reason": "เหตุผลในการเปลี่ยนแปลง"
}
5.3. Database Maintenance
Weekly Tasks:
- Check slow query log
- Optimize tables if needed:
OPTIMIZE TABLE document_number_counters; OPTIMIZE TABLE document_number_audit;
Monthly Tasks:
- Review and archive old audit logs (> 2 years)
- Check index usage:
SELECT * FROM sys.schema_unused_indexes WHERE object_schema = 'lcbp3_db';