Performance tuning

01.07.2026 | Muallif: Jaxongir a.k.a | Kategoriya: Spring Boot | 23 daqiqa o'qish

Performance tuning - Spring Boot application’ni tezroq, barqarorroq va kamroq resurs bilan ishlaydigan qilish jarayoni. Bu blok quyidagilarni qamrab oladi: Spring Boot startup time optimization, lazy bean initialization, HikariCP tuning, N+1 detection, bulk operations, batch inserts, @Async thread pool configuration.


1. Performance tuning nimadan boshlanadi?

Performance tuning - "hamma joyga cache qo‘shish" emas.

To‘g‘ri yondashuv:

Measure → Find bottleneck → Fix → Measure again

Ya’ni:

1. O‘lcha
2. Qayer sekinligini top
3. Faqat o‘sha joyni tuzat
4. Natijani qayta o‘lcha

Yomon yondashuv:

App sekin ekan → cache qo‘shamiz
App sekin ekan → WebFlux qilamiz
App sekin ekan → server kattalashtiramiz

Bu ko‘pincha muammoni yashiradi, hal qilmaydi.


2. Performance muammo qayerda bo‘lishi mumkin?

Spring Boot application’da bottleneck odatda quyidagi joylarda bo‘ladi:

Joy

Muammo

Startup

App juda sekin start bo‘ladi

Bean initialization

Keraksiz bean’lar startup’da yaratiladi

Database

Query sekin, index yo‘q, N+1 bor

JPA/Hibernate

Lazy/Eager noto‘g‘ri, entity graph yo‘q

Connection pool

HikariCP noto‘g‘ri sozlangan

HTTP client

Timeout yo‘q, threadlar band

Serialization

Katta JSON, ortiqcha field’lar

Async processing

Thread pool noto‘g‘ri

Logging

Juda ko‘p sync log yozish

Memory

Object allocation ko‘p, GC bosimi yuqori


3. Spring Boot startup time optimization

Muammo

Ba’zi Spring Boot loyihalar 30-90 sekundda start bo‘ladi.

Sabablar:

- Juda ko‘p dependency
- Keraksiz auto-configuration
- Startup’da DB/API chaqirish
- @PostConstruct ichida og‘ir logic
- Migration juda ko‘p vaqt oladi
- Bean initialization haddan tashqari ko‘p
- Component scan katta package’larni skan qiladi

Startup vaqtini o‘lchash

Spring Boot’da ApplicationStartup orqali startup step’larni ko‘rish mumkin.

@SpringBootApplication
public class App {

    public static void main(String[] args) {
        SpringApplication app = new SpringApplication(App.class);
        app.setApplicationStartup(new BufferingApplicationStartup(2048));
        app.run(args);
    }
}

Actuator bilan startup endpoint ochiladi:

management:
  endpoints:
    web:
      exposure:
        include: startup,health,info

Keyin:

GET /actuator/startup

Bu qaysi bean yoki auto-config qancha vaqt olganini ko‘rishga yordam beradi.


Startup’ni tezlashtirish yo‘llari

1. Keraksiz dependency’larni olib tashlash

Yomon:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-webflux</artifactId>
</dependency>

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>

Agar kerak bo‘lmasa, ikkala web stack’ni birga qo‘shmang.


2. Component scan’ni cheklash

Yomon:

@SpringBootApplication(scanBasePackages = "com")

Bu juda katta scope.

Yaxshi:

@SpringBootApplication(scanBasePackages = "uz.company.orderservice")

Yoki eng yaxshisi - main class root package’da bo‘lsin:

uz.company.orderservice
 ├── OrderServiceApplication.java
 ├── controller
 ├── service
 ├── repository

3. Startup’da external call qilmaslik

Yomon:

@PostConstruct
public void init() {
    bankClient.loadRates();
    externalApi.warmUp();
    reportService.generateCache();
}

Bu app startini sekinlashtiradi.

Yaxshi:

@EventListener(ApplicationReadyEvent.class)
public void warmUpAfterStartup() {
    cacheWarmupService.warmUpAsync();
}

Yoki alohida background job:

@Scheduled(fixedDelay = 300000)
public void refreshCache() {
    cacheService.refresh();
}

4. Lazy bean initialization

Nima?

Default holatda Spring singleton bean’larni startup paytida yaratadi.

App start
↓
Controller bean
Service bean
Repository bean
Client bean
Mapper bean
...

Lazy initialization yoqilsa, bean kerak bo‘lganda yaratiladi.

spring:
  main:
    lazy-initialization: true

Foydasi

Startup tezlashadi
Kam ishlatiladigan bean’lar keyin yaratiladi
Test startup tezlashishi mumkin

Xavfi

Lazy initialization xatoni startup’da emas, runtime’da chiqarishi mumkin.

Masalan:

PaymentClient noto‘g‘ri config qilingan

Lazy bo‘lmasa:

App start paytida xato beradi

Lazy bo‘lsa:

Birinchi payment request kelganda xato beradi

Shuning uchun production’da ehtiyotkorlik kerak.


Qachon ishlatish mumkin?

Holat

Lazy init

Local dev

Foydali

Test environment

Foydali

Production critical service

Ehtiyotkorlik bilan

Rarely used admin bean

Mos

Payment/security core bean

Tavsiya qilinmaydi


5. HikariCP tuning

Spring Boot’da JDBC connection pool sifatida odatda HikariCP ishlatiladi.

Connection pool nima?

Har bir DB query uchun yangi connection ochish qimmat.

Yomon:

Request keldi
↓
DB connection ochildi
↓
Query ishladi
↓
Connection yopildi

Yaxshi:

Connection pool oldindan connection saqlab turadi
Request kelganda tayyor connection beradi
Ish tugasa connection pool’ga qaytaradi

HikariCP asosiy config

spring:
  datasource:
    hikari:
      maximum-pool-size: 20
      minimum-idle: 5
      connection-timeout: 30000
      idle-timeout: 600000
      max-lifetime: 1800000

Tushuntirish:

Config

Ma’nosi

maximum-pool-size

Eng ko‘p connection soni

minimum-idle

Bo‘sh turgan minimal connection

connection-timeout

Connection kutish vaqti

idle-timeout

Ishlatilmayotgan connection qancha yashaydi

max-lifetime

Connection maksimal umri


maximum-pool-sizeni noto‘g‘ri oshirish

Ko‘p developer o‘ylaydi:

Pool size 10 sekin → 100 qilamiz

Bu har doim yaxshi emas.

Agar DB 100 connection’ni ko‘tara olmasa:

App tezlashmaydi
DB yanada sekinlashadi
Lock ko‘payadi
CPU oshadi

Pool size tanlash

Oddiy yondashuv:

maximum-pool-size = CPU core × 2 yoki workload’ga qarab 10-30

Lekin real production’da quyidagilar bilan o‘lchanadi:

- DB CPU
- query latency
- active connections
- pending threads
- connection acquisition time
- p95/p99 response time

HikariCP’da kuzatiladigan metrikalar

Actuator + Micrometer bilan:

hikaricp.connections.active
hikaricp.connections.idle
hikaricp.connections.pending
hikaricp.connections.timeout

Agar pending ko‘paysa:

Threadlar connection kutyapti

Sabablar:

- Pool kichik
- Query sekin
- Transaction juda uzoq
- Connection leak bor

6. Transaction performance

Eng katta xato: transaction ichida external call

Yomon:

@Transactional
public void createOrder(CreateOrderRequest request) {
    Order order = orderRepository.save(new Order(request));

    paymentClient.pay(order.getId()); // external HTTP call

    order.markPaid();
}

Bu xavfli.

Nima bo‘ladi?

Transaction ochiladi
DB connection olinadi
External payment API kutiladi
DB connection band turadi

Agar payment API 5 sekund kutsa:

DB connection ham 5 sekund band

Yaxshi:

@Transactional
public Order createOrder(CreateOrderRequest request) {
    return orderRepository.save(new Order(request));
}

public void processPayment(Long orderId) {
    paymentClient.pay(orderId);
}

Yoki outbox pattern:

Order yaratildi
↓
Outbox event yozildi
↓
Background worker payment jarayonini boshlaydi

7. N+1 problem

N+1 nima?

Masalan, 100 ta order olish kerak.

List<Order> orders = orderRepository.findAll();

for (Order order : orders) {
    System.out.println(order.getUser().getName());
}

Agar user lazy bo‘lsa, quyidagicha bo‘lishi mumkin:

SELECT * FROM orders; -- 1 query

SELECT * FROM users WHERE id = 1;
SELECT * FROM users WHERE id = 2;
SELECT * FROM users WHERE id = 3;
...
SELECT * FROM users WHERE id = 100;

Natija:

1 + 100 = 101 query

Shuning uchun nomi N+1.


N+1 qanday aniqlanadi?

1. SQL log yoqish

logging:
  level:
    org.hibernate.SQL: DEBUG

2. Hibernate statistics

spring:
  jpa:
    properties:
      hibernate:
        generate_statistics: true

3. p6spy yoki datasource-proxy

Bu production’ga yaqin query monitoring uchun foydali.

4. APM

New Relic
Datadog
Elastic APM
Grafana Tempo + metrics

N+1 fix: JOIN FETCH

@Query("""
    select o from Order o
    join fetch o.user
    where o.status = :status
""")
List<Order> findAllByStatusWithUser(OrderStatus status);

Bu bitta query bilan order va user’ni olib keladi.


N+1 fix: @EntityGraph

@EntityGraph(attributePaths = {"user"})
List<Order> findByStatus(OrderStatus status);

Bu Spring Data JPA’da qulayroq.


N+1 fix: DTO projection

Ko‘pincha eng yaxshi yechim - entity emas, kerakli DTO’ni olish.

@Query("""
    select new uz.company.dto.OrderListItemDto(
        o.id,
        o.status,
        u.name
    )
    from Order o
    join o.user u
    where o.status = :status
""")
List<OrderListItemDto> findOrderListItems(OrderStatus status);

Foydasi:

Kamroq column
Kamroq memory
Lazy loading muammosi yo‘q
API response uchun to‘g‘ri format

8. EAGER vs LAZY performance

EAGER xavfi

@ManyToOne(fetch = FetchType.EAGER)
private User user;

EAGER har doim bog‘liq entity’ni olib kelishga harakat qiladi.

Muammo:

Order kerak edi
User ham keldi
User profile ham keldi
Role ham keldi
Permission ham keldi

Natija:

Katta query
Ko‘p memory
Sekin response

Tavsiya

Ko‘p holatda:

@ManyToOne(fetch = FetchType.LAZY)
@OneToMany(fetch = FetchType.LAZY)
@ManyToMany(fetch = FetchType.LAZY)

Keyin kerak joyda:

JOIN FETCH
@EntityGraph
DTO projection

ishlatiladi.


9. Bulk operations

Muammo

1000 ta order statusini update qilish kerak.

Yomon:

List<Order> orders = orderRepository.findAllByStatus(OrderStatus.NEW);

for (Order order : orders) {
    order.setStatus(OrderStatus.EXPIRED);
}

orderRepository.saveAll(orders);

Bu ko‘p entity load qiladi, dirty checking ishlaydi, memory ishlatadi.


Yaxshi: bulk update

@Modifying
@Query("""
    update Order o
    set o.status = :newStatus
    where o.status = :oldStatus
""")
int updateStatus(
        @Param("oldStatus") OrderStatus oldStatus,
        @Param("newStatus") OrderStatus newStatus
);

Service:

@Transactional
public int expireOrders() {
    return orderRepository.updateStatus(OrderStatus.NEW, OrderStatus.EXPIRED);
}

Bu bitta SQL query:

UPDATE orders
SET status = 'EXPIRED'
WHERE status = 'NEW';

Bulk operation xavfi

Bulk update persistence context’ni chetlab o‘tadi.

Ya’ni oldin load qilingan entity eski holatda qolishi mumkin.

Shuning uchun kerak bo‘lsa:

@Modifying(clearAutomatically = true, flushAutomatically = true)

ishlatiladi.


10. Batch inserts

Muammo

Ko‘p row insert qilish:

for (Product product : products) {
    productRepository.save(product);
}

Bu juda ko‘p insert yuborishi mumkin.


Hibernate batch config

spring:
  jpa:
    properties:
      hibernate:
        jdbc:
          batch_size: 50
        order_inserts: true
        order_updates: true

Tushuntirish:

Config

Ma’nosi

batch_size: 50

50 ta insert/update bir batch

order_inserts

Insertlarni entity type bo‘yicha tartiblaydi

order_updates

Updatelarni tartiblaydi


ID generation muhim

Hibernate batch insert GenerationType.IDENTITY bilan ko‘pincha yomon ishlaydi, chunki DB har insertdan keyin ID qaytaradi.

Yaxshiroq:

@GeneratedValue(strategy = GenerationType.SEQUENCE)
private Long id;

PostgreSQL uchun sequence generator:

@Id
@GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "product_seq")
@SequenceGenerator(
        name = "product_seq",
        sequenceName = "product_seq",
        allocationSize = 50
)
private Long id;

Bu batch insert uchun ancha mos.


Juda katta importlarda flush/clear

@Transactional
public void importProducts(List<Product> products) {
    for (int i = 0; i < products.size(); i++) {
        entityManager.persist(products.get(i));

        if (i % 50 == 0) {
            entityManager.flush();
            entityManager.clear();
        }
    }
}

Bu persistence context haddan tashqari kattalashib ketmasligi uchun kerak.


11. Pagination performance

OFFSET pagination muammosi

SELECT *
FROM orders
ORDER BY created_at DESC
LIMIT 20 OFFSET 100000;

DB 100 000 row’ni o‘tkazib yuborib, keyingi 20 tasini beradi.

Katta jadvalda bu sekinlashadi.


Keyset pagination

Yaxshi:

SELECT *
FROM orders
WHERE created_at < '2026-05-21 10:00:00'
ORDER BY created_at DESC
LIMIT 20;

API:

GET /orders?cursor=2026-05-21T10:00:00&pageSize=20

Repository:

@Query("""
    select o from Order o
    where o.createdAt < :cursor
    order by o.createdAt desc
""")
List<Order> findNextPage(
        @Param("cursor") Instant cursor,
        Pageable pageable
);

Senior darajada katta dataset uchun OFFSET o‘rniga cursor/keyset pagination o‘ylash kerak.


12. @Async thread pool configuration

Muammo

@Async ishlatildi, lekin thread pool sozlanmadi.

Yomon:

@Async
public void sendEmail() {
    emailClient.send();
}

Default executor production uchun yetarli bo‘lmasligi mumkin.


To‘g‘ri executor config

@Configuration
@EnableAsync
public class AsyncConfig {

    @Bean(name = "notificationExecutor")
    public Executor notificationExecutor() {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        executor.setCorePoolSize(10);
        executor.setMaxPoolSize(30);
        executor.setQueueCapacity(500);
        executor.setThreadNamePrefix("notification-");
        executor.initialize();
        return executor;
    }
}

Ishlatish:

@Async("notificationExecutor")
public void sendNotification(NotificationRequest request) {
    notificationClient.send(request);
}

Thread pool parametrlari

Parametr

Ma’nosi

corePoolSize

Doimiy threadlar soni

maxPoolSize

Eng ko‘p threadlar soni

queueCapacity

Navbat hajmi

threadNamePrefix

Loglarda ajratish uchun nom


Eng katta xato: cheksiz queue

Agar queue juda katta bo‘lsa:

Requestlar yig‘iladi
Memory oshadi
Latency oshadi
App sekin o‘ladi

Yaxshi yondashuv:

Queue cheklangan bo‘lsin
Reject policy aniq bo‘lsin
Metrics kuzatilsin

13. HTTP client timeout tuning

External service chaqirganda timeout shart.

Yomon:

restTemplate.getForObject(url, Response.class);

Timeout yo‘q bo‘lsa, thread uzoq vaqt band bo‘lishi mumkin.


RestClient/WebClient timeout

WebClient Netty config bilan:

@Bean
public WebClient webClient() {
    HttpClient httpClient = HttpClient.create()
            .responseTimeout(Duration.ofSeconds(2))
            .option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 1000);

    return WebClient.builder()
            .clientConnector(new ReactorClientHttpConnector(httpClient))
            .build();
}

Timeout policy:

Connect timeout: 1s
Response timeout: 2s
Overall business timeout: 3s

14. JSON serialization performance

Muammo

Entity’ni to‘g‘ridan-to‘g‘ri response qilish:

@GetMapping("/{id}")
public Order getOrder(@PathVariable Long id) {
    return orderRepository.findById(id).orElseThrow();
}

Xavf:

Lazy loading
Recursive relationship
Keraksiz fieldlar
Katta JSON
Security leak

Yaxshi:

public record OrderResponse(
        Long id,
        String status,
        BigDecimal totalAmount,
        String customerName
) {}

Controller:

@GetMapping("/{id}")
public OrderResponse getOrder(@PathVariable Long id) {
    return orderService.getOrder(id);
}

Senior qoida:

API response uchun DTO ishlat.
Entity’ni tashqariga chiqarma.

15. Logging performance

Muammo

Juda ko‘p log ham performance’ni pasaytiradi.

Yomon:

log.info("Order payload: {}", objectMapper.writeValueAsString(order));

Bu log level o‘chiq bo‘lsa ham serialization bo‘lishi mumkin.

Yaxshi:

if (log.isDebugEnabled()) {
    log.debug("Order payload: {}", objectMapper.writeValueAsString(order));
}

Sensitive va katta loglardan qochish

Yomon:

Full request body
Full response body
Token
Password
Card number
Large JSON

Log quyidagicha bo‘lsin:

orderId=123 status=CREATED durationMs=45 traceId=abc123

16. Cache bilan performance tuning

Cache foydali, lekin noto‘g‘ri ishlatilsa eski data va bug beradi.

Qachon cache yaxshi?

- Data tez-tez o‘qiladi
- Kam o‘zgaradi
- Query qimmat
- Response deterministic

Misol:

@Cacheable(value = "products", key = "#id")
public ProductResponse getProduct(Long id) {
    return productRepository.findProductResponseById(id);
}

Update bo‘lsa:

@CacheEvict(value = "products", key = "#id")
public void updateProduct(Long id, UpdateProductRequest request) {
    productRepository.update(id, request);
}

Cache xavfi

- Stale data
- Cache stampede
- Memory oshishi
- Noto‘g‘ri key
- Tenant aralashib ketishi

Multi-tenant system’da cache key:

tenantId + productId

bo‘lishi kerak.


17. Performance checklist

[ ] Startup time o‘lchanganmi?
[ ] Keraksiz dependency olib tashlanganmi?
[ ] @PostConstruct ichida og‘ir logic yo‘qmi?
[ ] Lazy init qayerda xavfsizligi tekshirilganmi?
[ ] HikariCP metrics kuzatiladimi?
[ ] Transaction ichida external call yo‘qmi?
[ ] N+1 query tekshirilganmi?
[ ] EAGER relationshiplar kamaytirilganmi?
[ ] DTO projection ishlatilganmi?
[ ] Batch insert/update config qilinganmi?
[ ] OFFSET pagination katta jadvalda ishlatilmayaptimi?
[ ] @Async executor alohida sozlanganmi?
[ ] HTTP client timeout bor-mi?
[ ] Loglar structured va minimalmi?
[ ] Cache invalidation aniqmi?
[ ] p95/p99 latency monitoring bormi?

18. Real production misol

Muammo

GET /orders?page=5000&pageSize=20

Response sekin: 4.5s

Tekshiruv:

- OFFSET juda katta
- Order entity User bilan EAGER
- Har order uchun payment alohida query
- JSON response katta

Fix

1. Keyset pagination
2. DTO projection
3. JOIN kerak joyda
4. EAGER → LAZY
5. Index: created_at, status
6. Response’dan keraksiz fieldlarni olib tashlash

Natija:

4.5s → 120ms

Bu haqiqiy tuning: framework almashtirish emas, bottleneck’ni topib tuzatish.


19. Senior interview javob

Suhbatda so‘rasa:

Spring Boot performance tuningni qanday qilasiz?

Javob:

Avval o‘lchayman: metrics, logs, traces, DB query time, p95/p99 latency.
Keyin bottleneck topaman. Spring Boot’da ko‘p muammo DB query, N+1, noto‘g‘ri transaction, connection pool, timeout yo‘qligi yoki thread pool config’dan chiqadi.

Startup sekin bo‘lsa ApplicationStartup, Actuator startup endpoint, dependency va bean initialization’ni tekshiraman.
Runtime sekin bo‘lsa HikariCP metrics, SQL logs, Hibernate statistics, APM traces, thread dumps va GC metrics ko‘raman.

Fix sifatida DTO projection, JOIN FETCH, EntityGraph, batch insert, keyset pagination, Hikari tuning, HTTP timeout, bounded async executor va cache invalidation strategiyasini ishlataman.
Eng muhimi: har bir o‘zgarishdan keyin qayta o‘lchayman.

20. Qisqa xulosa

Performance tuning’da eng muhim qoida:

Guess qilmang. Measure qiling.

Senior developer quyidagilarni ajrata olishi kerak:

Muammo

To‘g‘ri yechim

Startup sekin

Dependency, bean init, startup profiling

Query sekin

Index, query optimization, DTO projection

N+1

JOIN FETCH, EntityGraph, projection

Connection kutish

Hikari metrics, transaction qisqartirish

Insert sekin

Batch insert, sequence generator

Katta pagination

Keyset pagination

Async sekin

Custom bounded executor

External API osilib qoladi

Timeout + CircuitBreaker

JSON katta

DTO, field kamaytirish

Log og‘ir

Structured minimal logging

Performance tuning - bu "tezlashtirish" emas. Bu resurs, latency, throughput va barqarorlikni nazorat qilish.