Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
package com.linkedin.openhouse.optimizer;

import com.linkedin.openhouse.optimizer.api.spec.ApiListLimitProperties;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.context.properties.EnableConfigurationProperties;

/** Spring Boot entry point for the Optimizer Service. */
@SpringBootApplication
@EnableConfigurationProperties(ApiListLimitProperties.class)
public class OptimizerServiceApplication {

public static void main(String[] args) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.linkedin.openhouse.optimizer.api.controller;

import com.linkedin.openhouse.optimizer.api.spec.ApiListLimitProperties;
import com.linkedin.openhouse.optimizer.api.spec.OperationStatus;
import com.linkedin.openhouse.optimizer.api.spec.OperationType;
import com.linkedin.openhouse.optimizer.api.spec.TableOperations;
Expand Down Expand Up @@ -27,6 +28,7 @@
public class TableOperationsController {

private final OptimizerDataService service;
private final ApiListLimitProperties limits;

/**
* Report an update to an operation. The body carries the {@code operationId} the caller is
Expand Down Expand Up @@ -59,24 +61,28 @@ public ResponseEntity<TableOperations> getTableOperation(@PathVariable String id
}

/**
* List operations matching the given filters. All parameters are optional — omit all to return
* every row.
* List operations matching the given filters. Every filter is optional. {@code limit} defaults to
* {@code optimizer.api.list.default-limit} (10) and is rejected with 400 outside {@code [1,
* optimizer.api.list.max-limit]} (1000).
*/
@GetMapping
public ResponseEntity<List<TableOperations>> listTableOperations(
@RequestParam(required = false) OperationType operationType,
@RequestParam(required = false) OperationStatus status,
@RequestParam(required = false) String databaseName,
@RequestParam(required = false) String tableName,
@RequestParam(required = false) String tableUuid) {
@RequestParam(required = false) String tableUuid,
@RequestParam(required = false) Integer limit) {
int effectiveLimit = limits.validateAndResolve(limit);
List<TableOperations> result =
service
.listTableOperations(
Optional.ofNullable(operationType).map(OperationType::toModel),
Optional.ofNullable(status).map(OperationStatus::toModel),
Optional.ofNullable(databaseName),
Optional.ofNullable(tableName),
Optional.ofNullable(tableUuid))
Optional.ofNullable(tableUuid),
effectiveLimit)
.stream()
.map(TableOperations::fromModel)
.collect(Collectors.toList());
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.linkedin.openhouse.optimizer.api.controller;

import com.linkedin.openhouse.optimizer.api.spec.ApiListLimitProperties;
import com.linkedin.openhouse.optimizer.api.spec.TableOperationsHistory;
import com.linkedin.openhouse.optimizer.service.OptimizerDataService;
import java.util.List;
Expand All @@ -22,6 +23,7 @@
public class TableOperationsHistoryController {

private final OptimizerDataService service;
private final ApiListLimitProperties limits;

/** Append a completed-job result. Called by the SparkJob after each run (success or failure). */
@PostMapping
Expand All @@ -31,12 +33,17 @@ public ResponseEntity<TableOperationsHistory> appendHistory(
.body(TableOperationsHistory.fromModel(service.appendHistory(dto.toModel())));
}

/** Return the most recent history for a table, newest first, up to {@code limit} rows. */
/**
* Return the most recent history for a table, newest first. {@code limit} defaults to {@code
* optimizer.api.list.default-limit} (10) and is rejected with 400 outside {@code [1,
* optimizer.api.list.max-limit]} (1000).
*/
@GetMapping("/{tableUuid}")
public ResponseEntity<List<TableOperationsHistory>> getHistory(
@PathVariable String tableUuid, @RequestParam(defaultValue = "100") int limit) {
@PathVariable String tableUuid, @RequestParam(required = false) Integer limit) {
int effectiveLimit = limits.validateAndResolve(limit);
List<TableOperationsHistory> result =
service.getHistory(tableUuid, limit).stream()
service.getHistory(tableUuid, effectiveLimit).stream()
.map(TableOperationsHistory::fromModel)
.collect(Collectors.toList());
return ResponseEntity.ok(result);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.linkedin.openhouse.optimizer.api.controller;

import com.linkedin.openhouse.optimizer.api.spec.ApiListLimitProperties;
import com.linkedin.openhouse.optimizer.api.spec.TableStats;
import com.linkedin.openhouse.optimizer.api.spec.TableStatsHistory;
import com.linkedin.openhouse.optimizer.api.spec.UpsertTableStatsRequest;
Expand All @@ -25,6 +26,7 @@
public class TableStatsController {

private final OptimizerDataService service;
private final ApiListLimitProperties limits;

/**
* Create or overwrite the stats row for {@code tableUuid}. Called by the Tables Service on every
Expand All @@ -48,37 +50,43 @@ public ResponseEntity<TableStats> getTableStats(@PathVariable String tableUuid)
}

/**
* List stats rows matching the given filters. All parameters are optional — omit all to return
* every row.
* List stats rows matching the given filters. Every filter is optional. {@code limit} defaults to
* {@code optimizer.api.list.default-limit} (10) and is rejected with 400 outside {@code [1,
* optimizer.api.list.max-limit]} (1000).
*/
@GetMapping
public ResponseEntity<List<TableStats>> listTableStats(
@RequestParam(required = false) String databaseName,
@RequestParam(required = false) String tableName,
@RequestParam(required = false) String tableUuid) {
@RequestParam(required = false) String tableUuid,
@RequestParam(required = false) Integer limit) {
int effectiveLimit = limits.validateAndResolve(limit);
List<TableStats> result =
service
.listTableStats(
Optional.ofNullable(databaseName),
Optional.ofNullable(tableName),
Optional.ofNullable(tableUuid))
Optional.ofNullable(tableUuid),
effectiveLimit)
.stream()
.map(TableStats::fromModel)
.collect(Collectors.toList());
return ResponseEntity.ok(result);
}

/**
* Return per-commit stats history for {@code tableUuid}, newest first. Optionally filter by
* {@code since} (inclusive) and cap at {@code limit} rows.
* Return per-commit stats history for {@code tableUuid}, newest first. Optional {@code since}
* filter (inclusive). {@code limit} defaults to {@code optimizer.api.list.default-limit} (10) and
* is rejected with 400 outside {@code [1, optimizer.api.list.max-limit]} (1000).
*/
@GetMapping("/{tableUuid}/history")
public ResponseEntity<List<TableStatsHistory>> getStatsHistory(
@PathVariable String tableUuid,
@RequestParam(required = false) Instant since,
@RequestParam(defaultValue = "100") int limit) {
@RequestParam(required = false) Integer limit) {
int effectiveLimit = limits.validateAndResolve(limit);
List<TableStatsHistory> result =
service.getStatsHistory(tableUuid, Optional.ofNullable(since), limit).stream()
service.getStatsHistory(tableUuid, Optional.ofNullable(since), effectiveLimit).stream()
.map(TableStatsHistory::fromModel)
.collect(Collectors.toList());
return ResponseEntity.ok(result);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package com.linkedin.openhouse.optimizer.api.spec;

import lombok.Getter;
import lombok.Setter;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.http.HttpStatus;
import org.springframework.web.server.ResponseStatusException;

/**
* Bounds on list-style endpoints. Default applies when the caller omits {@code limit}; max is the
* hard cap — requests beyond it are rejected (400) rather than silently clamped, so callers don't
* get partial results without knowing.
*/
@ConfigurationProperties("optimizer.api.list")
@Getter
@Setter
public class ApiListLimitProperties {
private int defaultLimit = 10;
private int maxLimit = 1000;

/**
* Resolve a caller-supplied {@code limit} against this configuration. Null falls back to {@link
* #defaultLimit}; out-of-range values throw {@link ResponseStatusException} carrying HTTP 400.
*/
public int validateAndResolve(Integer limit) {
int effective = (limit == null) ? defaultLimit : limit;
if (effective < 1 || effective > maxLimit) {
throw new ResponseStatusException(
HttpStatus.BAD_REQUEST,
"limit must be between 1 and " + maxLimit + " (got " + effective + ")");
}
return effective;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -23,15 +23,16 @@ public interface OptimizerDataService {
// --- TableOperations ---

/**
* List operations matching the given filters. Every parameter is optional — pass {@link
* Optional#empty()} to skip that filter. No filters returns all rows.
* List operations matching the given filters, capped at {@code limit} rows. Every filter
* parameter is optional — pass {@link Optional#empty()} to skip that filter.
*/
List<TableOperationDto> listTableOperations(
Optional<OperationTypeDto> operationType,
Optional<OperationStatusDto> status,
Optional<String> databaseName,
Optional<String> tableName,
Optional<String> tableUuid);
Optional<String> tableUuid,
int limit);

/**
* Update an operation by writing a history entry. Looks up the operation row by {@code
Expand Down Expand Up @@ -60,11 +61,14 @@ List<TableOperationDto> listTableOperations(
Optional<TableStatsDto> getTableStats(String tableUuid);

/**
* List stats rows matching the given filters. Every parameter is optional — pass {@link
* Optional#empty()} to skip that filter. No filters returns all rows.
* List stats rows matching the given filters, capped at {@code limit} rows. Every filter
* parameter is optional — pass {@link Optional#empty()} to skip that filter.
*/
List<TableStatsDto> listTableStats(
Optional<String> databaseName, Optional<String> tableName, Optional<String> tableUuid);
Optional<String> databaseName,
Optional<String> tableName,
Optional<String> tableUuid,
int limit);

/**
* Return per-commit stats history for {@code tableUuid}, newest first.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@
import java.util.UUID;
import java.util.stream.Collectors;
import lombok.RequiredArgsConstructor;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.domain.PageRequest;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
Expand All @@ -40,9 +39,6 @@ public class OptimizerDataServiceImpl implements OptimizerDataService {
private final TableStatsRepository statsRepository;
private final TableStatsHistoryRepository statsHistoryRepository;

@Value("${optimizer.repo.default-limit:10000}")
private int defaultLimit;

// --- TableOperations ---

@Override
Expand All @@ -51,7 +47,8 @@ public List<TableOperationDto> listTableOperations(
Optional<OperationStatusDto> status,
Optional<String> databaseName,
Optional<String> tableName,
Optional<String> tableUuid) {
Optional<String> tableUuid,
int limit) {
return operationsRepository
.find(
operationType.map(OperationTypeDto::toDb),
Expand All @@ -61,7 +58,7 @@ public List<TableOperationDto> listTableOperations(
tableName,
Optional.empty(),
Optional.empty(),
PageRequest.of(0, defaultLimit))
PageRequest.of(0, limit))
.stream()
.map(TableOperationDto::fromRow)
.collect(Collectors.toList());
Expand Down Expand Up @@ -137,8 +134,11 @@ public Optional<TableStatsDto> getTableStats(String tableUuid) {

@Override
public List<TableStatsDto> listTableStats(
Optional<String> databaseName, Optional<String> tableName, Optional<String> tableUuid) {
return statsRepository.find(databaseName, tableName, tableUuid, PageRequest.of(0, defaultLimit))
Optional<String> databaseName,
Optional<String> tableName,
Optional<String> tableUuid,
int limit) {
return statsRepository.find(databaseName, tableName, tableUuid, PageRequest.of(0, limit))
.stream()
.map(TableStatsDto::fromRow)
.collect(Collectors.toList());
Expand Down
3 changes: 2 additions & 1 deletion services/optimizer/src/main/resources/application.properties
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,8 @@ spring.datasource.username=${OPTIMIZER_DB_USER:oh_user}
spring.datasource.password=${OPTIMIZER_DB_PASSWORD:oh_password}
spring.datasource.hikari.maximum-pool-size=20

optimizer.repo.default-limit=10000
optimizer.api.list.default-limit=10
optimizer.api.list.max-limit=1000

management.endpoints.web.exposure.include=health,prometheus
management.endpoint.health.enabled=true
Loading