Skip to content

Commit 3bb542a

Browse files
committed
API redesign: three-layer find/decode/lookup to improve caching
1 parent bababd1 commit 3bb542a

9 files changed

Lines changed: 615 additions & 471 deletions

File tree

README.md

Lines changed: 312 additions & 157 deletions
Large diffs are not rendered by default.

benchmarks/inspect.zig

Lines changed: 2 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -18,15 +18,8 @@ pub fn main() !void {
1818
if (args.len > 1) db_path = args[1];
1919
if (args.len > 2) num_lookups = try std.fmt.parseUnsigned(u64, args[2], 10);
2020
if (args.len > 3) {
21-
var items: [max_mmdb_fields][]const u8 = undefined;
22-
23-
var it = std.mem.splitScalar(u8, args[3], ',');
24-
var i: usize = 0;
25-
while (it.next()) |part| : (i += 1) {
26-
items[i] = part;
27-
}
28-
29-
fields = items[0..i];
21+
const f = try maxminddb.Fields(max_mmdb_fields).parse(args[3], ',');
22+
fields = f.only();
3023
}
3124

3225
std.debug.print("Benchmarking with:\n", .{});

benchmarks/lookup.zig

Lines changed: 5 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -15,27 +15,22 @@ pub fn main() !void {
1515
var db_path: []const u8 = default_db_path;
1616
var num_lookups = default_num_lookups;
1717
var fields: ?[]const []const u8 = null;
18+
var index_bits: u8 = 16;
1819
if (args.len > 1) db_path = args[1];
1920
if (args.len > 2) num_lookups = try std.fmt.parseUnsigned(u64, args[2], 10);
2021
if (args.len > 3) {
21-
var items: [max_mmdb_fields][]const u8 = undefined;
22-
23-
var it = std.mem.splitScalar(u8, args[3], ',');
24-
var i: usize = 0;
25-
while (it.next()) |part| : (i += 1) {
26-
items[i] = part;
27-
}
28-
29-
fields = items[0..i];
22+
const f = try maxminddb.Fields(max_mmdb_fields).parse(args[3], ',');
23+
fields = f.only();
3024
}
25+
if (args.len > 4) index_bits = try std.fmt.parseUnsigned(u8, args[4], 10);
3126

3227
std.debug.print("Benchmarking with:\n", .{});
3328
std.debug.print(" Database: {s}\n", .{db_path});
3429
std.debug.print(" Lookups: {d}\n", .{num_lookups});
3530
std.debug.print("Opening database...\n", .{});
3631

3732
var open_timer = try std.time.Timer.start();
38-
var db = try maxminddb.Reader.mmap(allocator, db_path, .{ .ipv4_index_first_n_bits = 16 });
33+
var db = try maxminddb.Reader.mmap(allocator, db_path, .{ .ipv4_index_first_n_bits = index_bits });
3934
defer db.close();
4035
const open_time_ms = @as(f64, @floatFromInt(open_timer.read())) /
4136
@as(f64, @floatFromInt(std.time.ns_per_ms));

benchmarks/lookup_cache.zig

Lines changed: 9 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -17,15 +17,8 @@ pub fn main() !void {
1717
if (args.len > 1) db_path = args[1];
1818
if (args.len > 2) num_lookups = try std.fmt.parseUnsigned(u64, args[2], 10);
1919
if (args.len > 3) {
20-
var items: [max_mmdb_fields][]const u8 = undefined;
21-
22-
var it = std.mem.splitScalar(u8, args[3], ',');
23-
var i: usize = 0;
24-
while (it.next()) |part| : (i += 1) {
25-
items[i] = part;
26-
}
27-
28-
fields = items[0..i];
20+
const f = try maxminddb.Fields(max_mmdb_fields).parse(args[3], ',');
21+
fields = f.only();
2922
}
3023

3124
std.debug.print("Benchmarking with:\n", .{});
@@ -56,20 +49,20 @@ pub fn main() !void {
5649
std.crypto.random.bytes(&ip_bytes);
5750
const ip = std.net.Address.initIp4(ip_bytes, 0);
5851

59-
const result = db.lookupWithCache(
60-
maxminddb.geolite2.City,
61-
&cache,
62-
ip,
63-
.{ .only = fields },
64-
) catch |err| {
52+
const entry = db.find(ip, .{}) catch |err| {
6553
std.debug.print("! Lookup error for IP {any}: {any}\n", .{ ip, err });
6654
lookup_errors += 1;
6755
continue;
6856
};
69-
if (result == null) {
57+
if (entry == null) {
7058
not_found_count += 1;
7159
continue;
7260
}
61+
_ = cache.decode(&db, entry.?, .{ .only = fields }) catch |err| {
62+
std.debug.print("! Decode error for IP {any}: {any}\n", .{ ip, err });
63+
lookup_errors += 1;
64+
continue;
65+
};
7366
}
7467

7568
const elapsed_ns = timer.read();

benchmarks/scan.zig

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ const std = @import("std");
22
const maxminddb = @import("maxminddb");
33

44
const default_db_path: []const u8 = "GeoLite2-City.mmdb";
5+
const max_mmdb_fields = 32;
56

67
pub fn main() !void {
78
const allocator = std.heap.smp_allocator;
@@ -10,7 +11,12 @@ pub fn main() !void {
1011
defer std.process.argsFree(allocator, args);
1112

1213
var db_path: []const u8 = default_db_path;
14+
var fields: ?[]const []const u8 = null;
1315
if (args.len > 1) db_path = args[1];
16+
if (args.len > 2) {
17+
const f = try maxminddb.Fields(max_mmdb_fields).parse(args[2], ',');
18+
fields = f.only();
19+
}
1420

1521
std.debug.print("Benchmarking with:\n", .{});
1622
std.debug.print(" Database: {s}\n", .{db_path});
@@ -34,7 +40,7 @@ pub fn main() !void {
3440
std.debug.print("Starting benchmark...\n", .{});
3541
var timer = try std.time.Timer.start();
3642

37-
var it = try db.scan(maxminddb.any.Value, allocator, network, .{});
43+
var it = try db.scan(maxminddb.any.Value, allocator, network, .{ .only = fields });
3844

3945
var n: usize = 0;
4046
while (try it.next()) |item| {

benchmarks/scan_cache.zig

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ const std = @import("std");
22
const maxminddb = @import("maxminddb");
33

44
const default_db_path: []const u8 = "GeoLite2-City.mmdb";
5+
const max_mmdb_fields = 32;
56

67
pub fn main() !void {
78
const allocator = std.heap.smp_allocator;
@@ -10,7 +11,12 @@ pub fn main() !void {
1011
defer std.process.argsFree(allocator, args);
1112

1213
var db_path: []const u8 = default_db_path;
14+
var fields: ?[]const []const u8 = null;
1315
if (args.len > 1) db_path = args[1];
16+
if (args.len > 2) {
17+
const f = try maxminddb.Fields(max_mmdb_fields).parse(args[2], ',');
18+
fields = f.only();
19+
}
1420

1521
std.debug.print("Benchmarking with:\n", .{});
1622
std.debug.print(" Database: {s}\n", .{db_path});
@@ -37,15 +43,11 @@ pub fn main() !void {
3743
std.debug.print("Starting benchmark...\n", .{});
3844
var timer = try std.time.Timer.start();
3945

40-
var it = try db.scanWithCache(
41-
maxminddb.any.Value,
42-
&cache,
43-
network,
44-
.{},
45-
);
46+
var it = try db.entries(network, .{});
4647

4748
var n: usize = 0;
48-
while (try it.next()) |_| {
49+
while (try it.next()) |entry| {
50+
_ = try cache.decode(&db, entry, .{ .only = fields });
4951
n += 1;
5052
}
5153

src/filter.zig

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,12 @@ pub fn Fields(comptime capacity: usize) type {
2929
errdefer allocator.free(buf);
3030

3131
var f = try parse(buf, sep);
32+
// Don't keep the buffer if no fields were parsed, e.g., empty string.
33+
if (f.len == 0) {
34+
allocator.free(buf);
35+
return .{};
36+
}
37+
3238
f.buf = buf;
3339

3440
return f;
@@ -109,6 +115,22 @@ test "append trims whitespace-only to no-op" {
109115
try std.testing.expectEqual(null, f.only());
110116
}
111117

118+
test "parseAlloc empty string does not allocate" {
119+
var f = try Fields(4).parseAlloc(std.testing.allocator, "", ',');
120+
defer f.deinit(std.testing.allocator);
121+
122+
try std.testing.expectEqual(null, f.only());
123+
try std.testing.expectEqual(null, f.buf);
124+
}
125+
126+
test "parseAlloc whitespace-only does not allocate" {
127+
var f = try Fields(4).parseAlloc(std.testing.allocator, " , , ", ',');
128+
defer f.deinit(std.testing.allocator);
129+
130+
try std.testing.expectEqual(null, f.only());
131+
try std.testing.expectEqual(null, f.buf);
132+
}
133+
112134
test "parseAlloc frees buffer on TooManyFields" {
113135
const result = Fields(1).parseAlloc(std.testing.allocator, "a,b", ',');
114136
try std.testing.expectError(error.TooManyFields, result);

src/maxminddb.zig

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,9 @@ pub const geoip2 = @import("geoip2.zig");
1212

1313
pub const Error = reader.ReadError || decoder.DecodeError;
1414
pub const Reader = reader.Reader;
15-
pub const Result = reader.Result;
1615
pub const Metadata = reader.Metadata;
17-
pub const Iterator = reader.Iterator;
16+
pub const ResultIterator = reader.ResultIterator;
17+
pub const EntryIterator = reader.EntryIterator;
1818
pub const Cache = reader.Cache;
1919
pub const Network = net.Network;
2020
pub const Map = collection.Map;
@@ -904,7 +904,7 @@ test "decodeMetadata as any.Value" {
904904
var arena = std.heap.ArenaAllocator.init(allocator);
905905
defer arena.deinit();
906906

907-
const meta = try Reader.decodeMetadata(any.Value, arena.allocator(), db.src);
907+
const meta = try Metadata.decodeAs(any.Value, arena.allocator(), db.src);
908908
try expectEqualStrings("GeoLite2-City", meta.get("database_type").?.string);
909909
try expectEqual(@as(u16, 6), meta.get("ip_version").?.uint16);
910910
try expectEqual(@as(u16, 2), meta.get("binary_format_major_version").?.uint16);

0 commit comments

Comments
 (0)