Skip to content

Commit 0d3a188

Browse files
committed
in_tail: Use temp table on overflowing the limit of SQL statement
Signed-off-by: Hiroshi Hatake <hiroshi@chronosphere.io>
1 parent 27b6a19 commit 0d3a188

File tree

1 file changed

+160
-1
lines changed

1 file changed

+160
-1
lines changed

plugins/in_tail/tail_db.c

Lines changed: 160 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,8 @@
2525
#include "tail_sql.h"
2626
#include "tail_file.h"
2727

28+
#include <inttypes.h>
29+
2830
struct query_status {
2931
int id;
3032
int rows;
@@ -271,6 +273,138 @@ static int stmt_add_param_concat(struct flb_tail_config *ctx,
271273
return 0;
272274
}
273275

276+
/*
277+
* Scalable stale inode cleanup: use a temp table to avoid SQLite variable limits.
278+
*
279+
* The legacy implementation builds:
280+
* DELETE ... WHERE inode NOT IN (?,?,?,...);
281+
* which requires one bound parameter per inode and fails when the number of
282+
* monitored files exceeds SQLITE_LIMIT_VARIABLE_NUMBER (commonly 32766 in our
283+
* bundled SQLite, but can vary).
284+
*/
285+
static int flb_tail_db_stale_file_delete_temp_table(struct flb_tail_config *ctx,
286+
uint64_t file_count)
287+
{
288+
int ret;
289+
int changes;
290+
int txn_started = FLB_FALSE;
291+
sqlite3_stmt *stmt_insert_inode = NULL;
292+
struct mk_list *head;
293+
struct mk_list *tmp;
294+
struct flb_tail_file *file;
295+
296+
/* If there are no monitored files, delete everything from the DB table. */
297+
if (file_count == 0) {
298+
ret = flb_sqldb_query(ctx->db, "DELETE FROM in_tail_files;", NULL, NULL);
299+
if (ret != FLB_OK) {
300+
flb_plg_error(ctx->ins, "db: cannot delete all stale inodes (no monitored files)");
301+
goto error;
302+
}
303+
304+
changes = sqlite3_changes(ctx->db->handler);
305+
flb_plg_info(ctx->ins, "db: delete unmonitored stale inodes from the database: count=%d",
306+
changes);
307+
goto error;
308+
}
309+
310+
/* Create/clear temp table holding current monitored inodes. */
311+
ret = flb_sqldb_query(ctx->db,
312+
"CREATE TEMP TABLE IF NOT EXISTS in_tail_current_inodes ("
313+
" inode INTEGER PRIMARY KEY"
314+
");",
315+
NULL, NULL);
316+
if (ret != FLB_OK) {
317+
flb_plg_error(ctx->ins, "db: cannot create temp table for inode cleanup");
318+
tail_db_unlock(ctx);
319+
goto error;
320+
}
321+
322+
ret = flb_sqldb_query(ctx->db, "DELETE FROM in_tail_current_inodes;", NULL, NULL);
323+
if (ret != FLB_OK) {
324+
flb_plg_error(ctx->ins, "db: cannot clear temp inode table");
325+
goto error;
326+
}
327+
328+
/* Use a transaction for faster bulk inserts. */
329+
ret = flb_sqldb_query(ctx->db, "BEGIN;", NULL, NULL);
330+
if (ret != FLB_OK) {
331+
flb_plg_error(ctx->ins, "db: cannot begin transaction for temp inode inserts");
332+
goto error;
333+
}
334+
txn_started = FLB_TRUE;
335+
336+
ret = sqlite3_prepare_v2(ctx->db->handler,
337+
"INSERT OR IGNORE INTO in_tail_current_inodes(inode) VALUES (?);",
338+
-1, &stmt_insert_inode, 0);
339+
if (ret != SQLITE_OK) {
340+
flb_plg_error(ctx->ins, "db: cannot prepare temp inode insert statement, ret=%d", ret);
341+
goto error;
342+
}
343+
344+
mk_list_foreach_safe(head, tmp, &ctx->files_static) {
345+
file = mk_list_entry(head, struct flb_tail_file, _head);
346+
347+
ret = sqlite3_bind_int64(stmt_insert_inode, 1, (sqlite3_int64) file->inode);
348+
if (ret != SQLITE_OK) {
349+
flb_plg_error(ctx->ins, "db: error binding temp inode insert: inode=%" PRIu64 ", ret=%d",
350+
file->inode, ret);
351+
goto error;
352+
}
353+
354+
ret = sqlite3_step(stmt_insert_inode);
355+
if (ret != SQLITE_DONE) {
356+
flb_plg_error(ctx->ins, "db: error inserting inode into temp table: inode=%" PRIu64 ", ret=%d",
357+
file->inode, ret);
358+
goto error;
359+
}
360+
361+
sqlite3_clear_bindings(stmt_insert_inode);
362+
sqlite3_reset(stmt_insert_inode);
363+
}
364+
365+
sqlite3_finalize(stmt_insert_inode);
366+
stmt_insert_inode = NULL;
367+
368+
/* Delete any inode that is not in the current monitored set. */
369+
ret = flb_sqldb_query(ctx->db,
370+
"DELETE FROM in_tail_files "
371+
"WHERE inode NOT IN (SELECT inode FROM in_tail_current_inodes);",
372+
NULL, NULL);
373+
if (ret != FLB_OK) {
374+
flb_plg_error(ctx->ins, "db: cannot delete stale inodes using temp table");
375+
goto error;
376+
}
377+
378+
ret = flb_sqldb_query(ctx->db, "COMMIT;", NULL, NULL);
379+
if (ret != FLB_OK) {
380+
flb_plg_error(ctx->ins, "db: cannot commit transaction for temp inode inserts");
381+
goto error;
382+
}
383+
txn_started = FLB_FALSE;
384+
385+
changes = sqlite3_changes(ctx->db->handler);
386+
flb_plg_info(ctx->ins, "db: delete unmonitored stale inodes from the database: count=%d",
387+
changes);
388+
389+
tail_db_unlock(ctx);
390+
391+
return 0;
392+
393+
error:
394+
if (stmt_insert_inode) {
395+
sqlite3_finalize(stmt_insert_inode);
396+
}
397+
398+
if (txn_started == FLB_TRUE) {
399+
/* Best-effort rollback */
400+
flb_sqldb_query(ctx->db, "ROLLBACK;", NULL, NULL);
401+
}
402+
403+
tail_db_unlock(ctx);
404+
405+
return -1;
406+
}
407+
274408
int flb_tail_db_file_set(struct flb_tail_file *file,
275409
struct flb_tail_config *ctx)
276410
{
@@ -430,6 +564,7 @@ int flb_tail_db_stale_file_delete(struct flb_input_instance *ins,
430564
size_t sql_size;
431565
uint64_t idx;
432566
uint64_t file_count = ctx->files_static_count;
567+
int max_vars = -1;
433568
flb_sds_t stale_delete_sql;
434569
flb_sds_t sds_tmp;
435570
sqlite3_stmt *stmt_delete_inodes = NULL;
@@ -447,14 +582,38 @@ int flb_tail_db_stale_file_delete(struct flb_input_instance *ins,
447582
return -1;
448583
}
449584

585+
586+
/*
587+
* Avoid SQLite variable limits for large monitored file sets.
588+
*
589+
* sqlite3_limit(..., SQLITE_LIMIT_VARIABLE_NUMBER, -1) returns the current
590+
* runtime limit (compile-time hard limit may be higher). If our monitored
591+
* file count exceeds this, the legacy NOT IN (?,?,...) statement will fail
592+
* at prepare-time.
593+
*/
594+
max_vars = sqlite3_limit(ctx->db->handler, SQLITE_LIMIT_VARIABLE_NUMBER, -1);
595+
if (max_vars > 0 && file_count > (uint64_t) max_vars) {
596+
flb_plg_warn(ctx->ins,
597+
"db: large file set detected (%" PRIu64 " files) exceeds SQLite variable limit (%d); "
598+
"using temp-table cleanup for stale inode deletion",
599+
file_count, max_vars);
600+
return flb_tail_db_stale_file_delete_temp_table(ctx, file_count);
601+
}
602+
450603
/* Create a stmt sql buffer */
451604
sql_size = SQL_DELETE_STALE_FILE_START_LEN;
452605
sql_size += SQL_DELETE_STALE_FILE_WHERE_LEN;
453606
sql_size += SQL_STMT_START_PARAM_LEN;
454607
sql_size += SQL_STMT_PARAM_END_LEN;
455608
sql_size += SQL_STMT_END_LEN;
456609
if (file_count > 0) {
457-
sql_size += (SQL_STMT_ADD_PARAM_LEN * file_count);
610+
/*
611+
* We already account for the first '?' via SQL_STMT_START_PARAM_LEN.
612+
* Additional parameters are count-1 occurrences of ",?".
613+
*/
614+
if (file_count > 1) {
615+
sql_size += (SQL_STMT_ADD_PARAM_LEN * (file_count - 1));
616+
}
458617
}
459618

460619
stale_delete_sql = flb_sds_create_size(sql_size + 1);

0 commit comments

Comments
 (0)