Simple Log Service allows you to modify and delete data that has already been written to a Logstore. This topic describes how to enable the row-level update and delete feature, and how to use the OpenAPI or Python SDK to modify and delete log data by RowID or by query.
Overview
By default, a Logstore follows an append-only, immutable model. After data is written to a Logstore, it cannot be modified or deleted in place.
Some scenarios require you to modify or delete existing data in a Logstore:
Retroactive field correction: Update business fields such as order status, risk labels, or scoring results after the original log is written.
Test data cleanup: Remove test or canary records from a production Logstore.
Single-record repair: Fix a specific log entry during a business replay or reconciliation workflow.
To address these scenarios, Simple Log Service provides row-level update and delete capabilities on a per-Logstore basis. You can modify or delete individual records by RowID, or batch-modify or batch-delete records by query.
Note the following before you use this feature:
This feature is only available for newly created Logstores. Set
enableModifytotruewhen you create the Logstore. You cannot enable this feature for an existing Logstore.Once enabled, the feature cannot be disabled. Plan accordingly before you create the Logstore.
All modify and delete operations are irreversible. There is no built-in rollback mechanism.
Real-time consumers (LogHub) and delivery tasks are not notified of modifications or deletions. Only query-based read paths (Search, SQL, SPL) return the latest data.
Core concepts
RowID
RowID is a logical record identifier that Simple Log Service assigns to each log entry written to a Logstore with row-level update and delete enabled. All modify and delete operations use RowID to locate the target record.
Format: Simple Log Service generates the RowID at write time. You cannot customize or construct a RowID. Treat it as an opaque identifier.
Stability: A RowID remains unchanged throughout the lifecycle of a log record. Even after you modify a record, the same RowID still points to that record.
Visibility: Query results from a Logstore with this feature enabled automatically include the built-in field
__rowid__. This is a reserved field name and cannot be used in index configurations.
AffectedRows
Each successful modify or delete request returns AffectedRows, which indicates the number of log records that the operation actually affected. Use this value to verify that the operation performed as expected.
AffectedRows is returned in the HTTP response header x-log-affectedrows. In the Python SDK, read it through resp.affected_rows.
Prerequisites
Before you begin, make sure that you meet the following requirements:
A Simple Log Service project. For more information, see Create a project.
A Logstore with the
enableModifyparameter set totrue. See Enable the feature in this topic.The
log:UpdateLogStoreLogsandlog:DeleteLogStoreLogsRAM permissions granted to the caller. See Required RAM permissions in this topic.(Conditional) If you use query-based operations, the fields referenced in the query must have indexes configured.
Enable the feature
To use row-level update and delete, set the enableModify parameter to true when you call the CreateLogStore API.
You can only set
enableModifywhen you create a new Logstore. You cannot enable this feature for an existing Logstore.After you set
enableModifytotrue, you cannot disable it.This feature can only be enabled through the OpenAPI or SDK. The Simple Log Service console does not support this feature.
Enable through OpenAPI
Call the CreateLogStore API and set enableModify to true in the request body:
POST /logstores HTTP/1.1
Host: <project>.<endpoint>
Content-Type: application/json
{
"logstoreName": "my-logstore",
"ttl": 30,
"shardCount": 1,
"enableModify": true
}
After you create the Logstore, call the GetLogStore API to verify. If the response body contains "enableModify": true, the feature is enabled.
Enable through the Python SDK
from aliyun.log import LogClient
# Obtain AccessKey credentials from environment variables.
client = LogClient(endpoint, access_key_id, access_key)
client.create_logstore(
project_name="my-project",
logstore_name="my-logstore",
ttl=30,
shard_count=1,
enable_modify=True,
)
Modify and delete data
Simple Log Service supports two methods for modifying and deleting log data:
Method |
Scenario |
Maximum rows per request |
By RowID |
You know the exact RowID of the target record and want to modify or delete it precisely. |
1 |
By query |
You want to batch-modify or batch-delete all records that match a query condition. |
10,000 |
Method 1: Modify and delete by RowID
Use this method when you already have the RowID of a specific record, typically obtained from a previous query result.
Modify a single record
Specify only the fields that you want to change. Fields that you do not include remain unchanged.
from aliyun.log import LogClient, LogItem
client = LogClient(endpoint, access_key_id, access_key)
# Specify only the fields to update. Unspecified fields keep their original values.
new_item = LogItem(
contents=[("status", "REFUNDED")],
)
resp = client.update_logs(
project="my-project",
logstore="my-logstore",
rowid=rowid,
log_item=new_item,
)
print(f"Affected rows: {resp.affected_rows}")
Delete a single record
resp = client.delete_logs_v2(
project="my-project",
logstore="my-logstore",
rowid=rowid,
)
print(f"Affected rows: {resp.affected_rows}")
Deleting the same record multiple times is idempotent. The operation does not return an error, but
affected_rowsreturns0.Use
delete_logs_v2for synchronous delete. The legacydelete_logsmethod performs an asynchronous soft delete, which is not supported for Logstores withenableModifyenabled.
Method 2: Modify and delete by query
Use this method to batch-modify or batch-delete records that match a query condition. Simple Log Service runs the query on the server side and applies the modification or deletion to all matching records.
This operation is irreversible.
If the operation partially fails, already-modified or already-deleted rows are not rolled back. The response returns only the number of rows that were actually affected.
Before you execute, use Search or SQL to preview the records that match your query condition and verify the scope.
Query-based batch operations are resource-intensive. Narrow the time range or refine the query condition to stay within the 10,000-row limit per request.
The fields referenced in the query must have indexes configured. Otherwise, the query returns no results.
Delete by query
resp = client.delete_logs_v2(
project="my-project",
logstore="my-logstore",
from_time=1716537600,
to_time=1716624000,
query='level: DEBUG',
)
print(f"Deleted rows: {resp.affected_rows}")
Modify by query
Specify both the query condition and the field values to update. All matching records are updated with the same field values.
resp = client.update_logs(
project="my-project",
logstore="my-logstore",
from_time=1716537600,
to_time=1716624000,
query='order_id: 12345 and status: "PENDING"',
update_fields={"status": "REFUNDED", "refund_at": "2026-05-25T10:00:00Z"},
)
print(f"Modified rows: {resp.affected_rows}")
If you pass both
rowidandqueryin the same request,rowidtakes precedence and thequeryparameter is ignored.Query-based operations require the data to be query-visible. There may be a delay of a few seconds between data ingestion and query availability.
Verify results
After you run a modify or delete operation, verify the result to confirm the operation succeeded as expected.
-
Check AffectedRows: Each request returns
AffectedRowsin the response (HTTP headerx-log-affectedrowsorresp.affected_rowsin the Python SDK). Compare this value against the expected number of records:AffectedRows > 0and matches expectation: The operation succeeded.AffectedRows = 0: No records matched the condition, or the record was already deleted. Re-check your query or RowID.AffectedRowsis lower than expected: A partial failure may have occurred. Re-run the operation for the remaining records.
-
Run a follow-up query: For batch operations, run a Search or SQL query after the operation to confirm that the data reflects the expected state. This is especially important when
AffectedRowsis lower than expected.
Data consistency
Modify and delete operations take effect synchronously. After a request succeeds, subsequent queries (Search, SQL, and SPL) return the updated results.
Note the following behaviors:
-
Real-time consumption is not affected: LogHub consumers and delivery tasks (such as delivery to OSS or MaxCompute) operate on the original write stream. They do not reflect modifications or deletions. Only query-based read paths return the latest data.
-
Query-based operations are not atomic: The server processes each matching record individually. If a partial failure occurs, already-processed records are not rolled back. The response returns the number of rows that were actually affected.
Required RAM permissions
The Resource Access Management (RAM) user or role that calls the modify or delete API must have the following permissions:
Action |
Description |
|
Modify log data by RowID or by query. |
|
Delete log data by RowID or by query. |
Resource ARN format:
acs:log:<region>:<account-id>:project/<project-name>/logstore/<logstore-name>
We recommend that you create a dedicated RAM user or role for modify and delete operations, especially for query-based batch deletions. Use ActionTrail to audit these operations.
Usage limits
Item |
Description |
New Logstore only |
You must set |
Cannot be disabled |
After you enable this feature, you cannot disable it. |
Maximum rows per query-based operation |
10,000 rows per request. If the query matches more than 10,000 records, the request is rejected. Narrow the time range or refine the query condition. |
Index requirement |
Fields used as query conditions must have indexes configured. Operations by RowID do not require business field indexes. |
No index rebuild |
After you enable this feature, the Logstore does not support index rebuilds. |
No soft delete |
After you enable this feature, asynchronous soft delete is not supported. Use the synchronous delete interface ( |
|
You cannot use |
Real-time consumers not notified |
LogHub consumers and delivery tasks (such as delivery to OSS or MaxCompute) are not notified of modifications or deletions. Only query-based read paths (Search, SQL, SPL) return the latest data. |
Billing
Modify operations: Each modify operation generates additional index storage and index traffic charges for the updated data.
Delete operations: Delete operations do not incur additional charges.
FAQ
Can I enable this feature for an existing Logstore?
No. This feature depends on an underlying storage format that is only available at Logstore creation time. To use this feature, create a new Logstore with enableModify=true, and migrate historical data to the new Logstore through data transformation or the SDK.
Do modify and delete operations affect real-time consumers?
No. LogHub consumers and delivery tasks (such as delivery to OSS or MaxCompute) operate on the original write stream and do not reflect modifications or deletions. Only query-based read paths (Search, SQL, and SPL) return the latest data.
Are modifications and deletions immediately visible?
Yes. After a request succeeds, new queries return the updated data. The AffectedRows value in the response indicates the number of records that were actually modified or deleted.
Can I roll back an accidental modification or deletion?
No. There is no built-in rollback mechanism. Before you run a modify or delete operation, use Search or SQL to preview the records that match your query condition. We recommend that you use dedicated RAM users with restricted permissions for these operations and enable ActionTrail for auditing.
Does deletion immediately free up storage space?
Deletion is a logical operation. The underlying storage is retained until the Logstore TTL (time to live) expires or a background compaction task runs. However, all query paths filter out deleted records, so deleted data is invisible to users.