Unify vehicle metadata and driving trajectories in a single Tablestore timeseries table, and leverage Lastpoint and search indexes for multi-condition vehicle searches and trajectory replay.
Solution overview
A shared car management platform needs vehicle management, trajectory storage, and order management capabilities. Vehicle management maintains basic vehicle information and real-time status, supporting searches for available vehicles by location, model, range, and other conditions. Trajectory storage persists monitoring data reported every 10 seconds during driving — including position, speed, and range — for billing and trajectory replay.
This data has two key characteristics:
-
Vehicle metadata ranges from hundreds of thousands to tens of millions of records and requires multi-condition searches.
-
Trajectory and status data is typical time series data. Write throughput can reach tens of thousands to millions of records per second, with daily accumulation at the billion to tens-of-billions level. Data lifecycle management is required to control storage costs.
A traditional approach uses MySQL for vehicle information and HBase for trajectory data, with Solr for search. This architecture is complex and requires operating and maintaining multiple components. The Tablestore timeseries model stores both vehicle metadata and trajectory data in a single timeseries table. A Lastpoint index retrieves the latest status of each vehicle, and a search index enables geo-location queries and multi-condition searches — eliminating the need for additional search components.
Solution design
This solution uses one timeseries table for all vehicle data. Each vehicle maps to a timeline: vehicle information is stored as timeline metadata, while driving trajectories and status data are stored as data points on the timeline.
|
Timeseries concept |
Vehicle data mapping |
Description |
|
measurementName |
Platform identifier |
Distinguishes different shared car platforms. Example: |
|
dataSource |
Platform name |
Distinguishes different platform entities under the same measurement. Example: |
|
tags |
Vehicle identifier |
Uniquely identifies a vehicle. Example: |
|
attributes |
Vehicle metadata |
Stores basic vehicle properties such as model, plate, color, seats, range_km, status, and location. Call QueryTimeseriesMeta to search by attributes. |
|
timeInUs + fields |
Trajectory and status data points |
Records the latitude, longitude, speed, range_km, and order_id for each reporting interval, with timestamps in microseconds. |
To support vehicle searches, create a Lastpoint index on the timeseries table to retrieve each vehicle's latest data point. Then create a search index on the Lastpoint index to enable combined queries by vehicle status, geo-location, range, and other conditions.
Implementation
Build the data layer of a shared car management platform with the Tablestore Java SDK by following these steps.
This solution uses V4 signature authentication to connect to a Tablestore timeseries instance. Add Tablestore SDK 5.17.4 or later to your Maven dependencies. Create a timeseries instance in the Tablestore console before you begin.
Download the sample code to run this solution directly.
-
Sample code download: car.demo.zip.
-
Before you run the code, set the following environment variables to point to your timeseries instance:
OTS_ENDPOINT,OTS_AK_ENV,OTS_SK_ENV,OTS_INSTANCE, andOTS_REGION. -
Run
SharedCarDemoto create the table, register vehicles, upload trajectories, search vehicles, and query trajectories in sequence.
Step 1: Initialize the client and create a timeseries table
Build a TimeseriesClient with V4 signature credentials and create a timeseries table with a data TTL.
// Build V4 signature credentials
String accessKeyId = System.getenv("TABLESTORE_ACCESS_KEY_ID");
String accessKeySecret = System.getenv("TABLESTORE_ACCESS_KEY_SECRET");
String region = "cn-hangzhou";
String instanceName = "your-timeseries-instance";
String endpoint = "https://your-timeseries-instance.cn-hangzhou.ots.aliyuncs.com";
DefaultCredentials credentials = new DefaultCredentials(accessKeyId, accessKeySecret);
V4Credentials v4Credentials = V4Credentials.createByServiceCredentials(credentials, region);
CredentialsProvider provider = new DefaultCredentialProvider(v4Credentials);
TimeseriesClient client = new TimeseriesClient(
endpoint, provider, instanceName, null, new ResourceManager(null, null));
// Create timeseries table with 90-day TTL
TimeseriesTableMeta tableMeta = new TimeseriesTableMeta("shared_car_track");
tableMeta.setTimeseriesTableOptions(new TimeseriesTableOptions(90 * 24 * 3600));
CreateTimeseriesTableRequest request = new CreateTimeseriesTableRequest(tableMeta);
request.setEnableAnalyticalStore(false);
client.createTimeseriesTable(request);
Step 2: Register vehicles (write timeline metadata)
Use UpdateTimeseriesMeta to write vehicle information to timeline attributes. Each vehicle maps to one timeline, uniquely identified by the combination of measurementName, dataSource, and tags.
// Register a car by writing timeline metadata
Map<String, String> tags = new TreeMap<>();
tags.put("car_id", "CAR_001");
TimeseriesKey key = new TimeseriesKey("shared_car", "platform_a", tags);
TimeseriesMeta meta = new TimeseriesMeta(key);
Map<String, String> attrs = new HashMap<>();
attrs.put("model", "BYD Qin Plus");
attrs.put("plate", "ZA12345");
attrs.put("color", "White");
attrs.put("seats", "5");
attrs.put("range_km", "120");
attrs.put("status", "idle");
attrs.put("location", "30.2741,120.1551");
meta.setAttributes(attrs);
UpdateTimeseriesMetaRequest request = new UpdateTimeseriesMetaRequest("shared_car_track");
request.setMetas(Collections.singletonList(meta));
UpdateTimeseriesMetaResponse response = client.updateTimeseriesMeta(request);
if (!response.isAllSuccess()) {
for (UpdateTimeseriesMetaResponse.FailedRowResult fail : response.getFailedRows()) {
System.out.println("Failed: " + fail.getIndex() + " - " + fail.getError());
}
}
Step 3: Upload trajectory data
During driving, vehicles upload a monitoring data point every 10 seconds, including latitude, longitude, speed, range, and the associated order ID. Use PutTimeseriesData to write data points in batches.
// Upload tracking data points
List<TimeseriesRow> rows = new ArrayList<>();
Map<String, String> tags = new TreeMap<>();
tags.put("car_id", "CAR_001");
TimeseriesKey key = new TimeseriesKey("shared_car", "platform_a", tags);
// Simulate 10 data points at 10-second intervals
long baseTime = System.currentTimeMillis();
for (int i = 0; i < 10; i++) {
long timeUs = (baseTime - (10 - i) * 10000) * 1000; // Microseconds
TimeseriesRow row = new TimeseriesRow(key, timeUs);
row.addField("latitude", ColumnValue.fromDouble(30.2741 + i * 0.001));
row.addField("longitude", ColumnValue.fromDouble(120.1551 + i * 0.0015));
row.addField("speed", ColumnValue.fromLong(30 + (long)(Math.random() * 50)));
row.addField("range_km", ColumnValue.fromLong(120 - i * 2));
row.addField("order_id", ColumnValue.fromString("ORDER_001"));
rows.add(row);
}
PutTimeseriesDataRequest request = new PutTimeseriesDataRequest("shared_car_track");
request.setRows(rows);
PutTimeseriesDataResponse response = client.putTimeseriesData(request);
if (!response.isAllSuccess()) {
for (PutTimeseriesDataResponse.FailedRowResult fail : response.getFailedRows()) {
System.out.println("Failed: " + fail.getIndex() + " - " + fail.getError());
}
}
Step 4: Search vehicles
Use QueryTimeseriesMeta to search vehicles by timeline attributes. The following example queries all idle vehicles. You can combine additional conditions such as model and range.
// Search idle vehicles by metadata attributes
QueryTimeseriesMetaRequest request = new QueryTimeseriesMetaRequest("shared_car_track");
CompositeMetaQueryCondition condition = new CompositeMetaQueryCondition(
MetaQueryCompositeOperator.OP_AND);
condition.addSubCondition(new MeasurementMetaQueryCondition(
MetaQuerySingleOperator.OP_EQUAL, "shared_car"));
condition.addSubCondition(new AttributeMetaQueryCondition(
MetaQuerySingleOperator.OP_EQUAL, "status", "idle"));
request.setCondition(condition);
request.setGetTotalHits(true);
request.setLimit(100);
QueryTimeseriesMetaResponse response = client.queryTimeseriesMeta(request);
System.out.println("Idle vehicles: " + response.getTotalHits());
for (TimeseriesMeta meta : response.getTimeseriesMetas()) {
System.out.println(" Vehicle: " + meta.getTimeseriesKey().getTags()
+ ", Model: " + meta.getAttributes().get("model")
+ ", Range: " + meta.getAttributes().get("range_km") + " km");
}
To search for nearby vehicles by geo-location, create a Lastpoint index and a search index, then use GeoDistanceQuery for radius-based searches.
// Create Lastpoint index for latest data point access
CreateTimeseriesLastpointIndexRequest lpRequest =
new CreateTimeseriesLastpointIndexRequest("shared_car_track", "car_lastpoint_index", true);
client.createTimeseriesLastpointIndex(lpRequest);
// Create search index on Lastpoint index for geo queries
// Then use SyncClient to query with GeoDistanceQuery + TermQuery(status=idle)
Step 5: Query trajectories
Use GetTimeseriesData to retrieve the historical trajectory of a vehicle by timeline and time range. Data can be read in forward or backward order.
// Query the trajectory of a specific vehicle within a time range
GetTimeseriesDataRequest request = new GetTimeseriesDataRequest("shared_car_track");
Map<String, String> tags = new TreeMap<>();
tags.put("car_id", "CAR_001");
TimeseriesKey key = new TimeseriesKey("shared_car", "platform_a", tags);
request.setTimeseriesKey(key);
// Query the last hour
long now = System.currentTimeMillis() * 1000; // Microseconds
request.setTimeRange(now - 3600L * 1000 * 1000 * 1000, now);
request.setLimit(100);
GetTimeseriesDataResponse response = client.getTimeseriesData(request);
for (TimeseriesRow row : response.getRows()) {
Map<String, ColumnValue> fields = row.getFields();
System.out.printf("Time: %d, Lat: %.6f, Lng: %.6f, Speed: %d km/h, Range: %d km%n",
row.getTimeInUs() / 1000,
fields.get("latitude").asDouble(),
fields.get("longitude").asDouble(),
fields.get("speed").asLong(),
fields.get("range_km").asLong());
}
Set the backward parameter to true to read data in reverse order and retrieve the latest status of a vehicle.
// Get the latest data point of a vehicle (backward query)
GetTimeseriesDataRequest request = new GetTimeseriesDataRequest("shared_car_track");
request.setTimeseriesKey(key);
request.setTimeRange(0, System.currentTimeMillis() * 1000);
request.setBackward(true);
request.setLimit(1);
GetTimeseriesDataResponse response = client.getTimeseriesData(request);
Clean up resources
The following operations delete the timeseries table along with all its data and indexes. This action is irreversible. Make sure that you no longer need the data before you proceed.
// Delete the timeseries table and all its data
DeleteTimeseriesTableRequest request = new DeleteTimeseriesTableRequest("shared_car_track");
client.deleteTimeseriesTable(request);