Гео-команды: координаты и радиус
Гео-команды Redis позволяют хранить координаты объектов и быстро находить всё, что лежит в заданном радиусе или прямоугольнике вокруг точки.
Geo-команды (
GEOADD,GEOSEARCH,GEODIST,GEOPOS) — это надстройка над sorted set: координаты (долгота, широта) кодируются в одно число (geohash), которое становится score, а имя объекта — членом множества.
«Найди рестораны рядом», «покажи свободные самокаты в радиусе 300 метров», «какие курьеры ближе всего к точке выдачи» — за всеми этими фичами стоит одна операция: поиск объектов вокруг координаты. Redis умеет это из коробки, без отдельной геобазы, и отвечает за доли миллисекунды. В этом уроке разберём команды и заодно поймём, почему гео в Redis — это, по сути, тот же sorted set, что вы уже знаете.
GEOADD: добавляем объекты с координатами
GEOADD key longitude latitude member добавляет объект. Важен порядок: сначала долгота (longitude), потом широта (latitude) — частая путаница. Заведём несколько кафе в центре Петербурга.
GEOADD cafes 30.3209 59.9410 "Кофе у Дома"
GEOADD cafes 30.3146 59.9398 "Эрмитаж-кафе"
GEOADD cafes 30.4500 59.8900 "Дальний угол"
Под капотом это обычный ZADD: член — название кафе, а score — число, в которое упакованы обе координаты. Поэтому к гео-ключу применимы и команды sorted set: ZCARD cafes вернёт число объектов, ZREM cafes "Дальний угол" удалит точку.
GEODIST: расстояние между объектами
GEODIST key member1 member2 [unit] возвращает расстояние между двумя сохранёнными объектами. Единицы: m (по умолчанию), km, mi, ft.
GEODIST cafes "Кофе у Дома" "Эрмитаж-кафе" m # => "около 350"
GEODIST cafes "Кофе у Дома" "Дальний угол" km # => "около 8"
GEOSEARCH: поиск «рядом»
Главная команда. GEOSEARCH ищет объекты вокруг центра — либо вокруг координаты (FROMLONLAT), либо вокруг уже сохранённого объекта (FROMMEMBER), в радиусе (BYRADIUS) или в прямоугольнике (BYBOX). Модификаторы WITHCOORD, WITHDIST, ASC/DESC, COUNT управляют выводом.
# ближайшие кафе в радиусе 1 км от точки (я стою тут), с расстоянием, по возрастанию
GEOSEARCH cafes FROMLONLAT 30.3158 59.9398 BYRADIUS 1000 m ASC WITHDIST
# 1) "Эрмитаж-кафе" 67.xxxx
# 2) "Кофе у Дома" 314.xxxx
# "Дальний угол" не попал — он дальше 1 км
В современных Redis именно GEOSEARCH заменяет устаревшие GEORADIUS и GEORADIUSBYMEMBER — у новой команды единый, более гибкий интерфейс.
Как это работает под капотом
Координаты кодируются алгоритмом geohash: пространство рекурсивно делится пополам по долготе и широте, и из чередующихся бит складывается одно 52-битное целое. Соседние по этому числу значения — это, как правило, географически близкие точки. Это число и есть score в sorted set, а sorted set хранит элементы отсортированными по score. Поэтому поиск «в радиусе» сводится к выборке диапазонов соседних score (что sorted set делает мгновенно) и финальной проверке честного расстояния.
Само расстояние Redis считает по формуле гаверсинусов (haversine) — расстоянию по дуге сферы. Воспроизведём фильтр «в радиусе 1 км, отсортировать по близости» на чистом Python, чтобы увидеть, что делает GEOSEARCH ... BYRADIUS после выборки кандидатов:
from math import radians, sin, cos, asin, sqrt
def haversine(lon1, lat1, lon2, lat2):
R = 6372797.56 # радиус Земли в метрах, как в Redis
lon1, lat1, lon2, lat2 = map(radians, (lon1, lat1, lon2, lat2))
dlon, dlat = lon2 - lon1, lat2 - lat1
a = sin(dlat/2)**2 + cos(lat1)*cos(lat2)*sin(dlon/2)**2
return 2 * R * asin(sqrt(a))
me = (30.3158, 59.9398) # центр поиска (lon, lat)
cafes = [
("Кофе у Дома", 30.3209, 59.9410),
("Эрмитаж-кафе", 30.3146, 59.9398),
("Дальний угол", 30.4500, 59.8900),
]
found = []
for name, lon, lat in cafes:
d = haversine(me[0], me[1], lon, lat)
if d <= 1000: # BYRADIUS 1000 m
found.append((round(d), name))
found.sort() # ASC по расстоянию
print("Кафе в радиусе 1 км (ASC):")
for dist, name in found:
print(f" {name:14} {dist} м")
Вывод:
Кафе в радиусе 1 км (ASC): Эрмитаж-кафе 67 м Кофе у Дома 314 м
«Дальний угол» отсеялся — он за пределами километра. Redis делает то же самое, только кандидатов отбирает не перебором всех точек, а по диапазонам geohash в sorted set, поэтому работает быстро даже на миллионах объектов.
Частые ошибки
- Перепутать порядок координат. В
GEOADDиGEOSEARCH FROMLONLATсначала долгота, потом широта. Если поменять местами — точки окажутся «в океане». - Выход за пределы координат. Долгота должна быть в диапазоне примерно ±180, широта — примерно ±85.05 (ограничение проекции). Координаты вне диапазона Redis отвергнет ошибкой.
- Огромный радиус на большом множестве.
BYRADIUSв тысячи км вернёт почти весь набор; всегда ограничивайте выдачу черезCOUNT. - Полагаться на устаревшие команды.
GEORADIUSобъявлены устаревшими; в новом коде используйтеGEOSEARCHиGEOSEARCHSTORE. - Ждать точности до сантиметра. Geohash — приближение: на 52 битах ошибка кодирования в пределах метра. Для городской логистики это незаметно, для геодезии — нет.
Итоги
- Гео в Redis — это sorted set, где score — закодированные координаты (geohash), а member — имя объекта.
GEOADDдобавляет точку (долгота, затем широта!),GEODISTмеряет расстояние,GEOSEARCHищет в радиусе или прямоугольнике.- Поиск «рядом» быстр, потому что сводится к диапазонам соседних score в sorted set плюс проверка честного расстояния по haversine.
- Используйте
GEOSEARCHвместо устаревшихGEORADIUS, ограничивайте выдачу черезCOUNTи не путайте порядок координат.